2009-07-22 55 views
45

Có cách nào để kiểm tra xem có tồn tại enum hay không bằng cách so sánh nó với một chuỗi đã cho? Tôi dường như không thể tìm thấy bất kỳ chức năng nào như vậy. Tôi chỉ có thể thử sử dụng phương pháp valueOf và bắt một ngoại lệ nhưng tôi đã được dạy rằng bắt ngoại lệ thời gian chạy không phải là thực hành tốt. Có ai có ý kiến ​​gì không?Kiểm tra xem có tồn tại enum trong Java

+6

Tôi thực sự không hiểu ý tưởng đằng sau giá trị emumOf ném một ngoại lệ ... nó không có ý nghĩa gì cả. Nó sẽ thực tế hơn rất nhiều trong mọi khía cạnh nếu nó chỉ trả về NULL. – marcolopes

Trả lời

41

Tôi không nghĩ rằng có một cách tích hợp để làm điều đó mà không bị bắt ngoại lệ. Bạn có thể sử dụng thay vì một cái gì đó như thế này:

public static MyEnum asMyEnum(String str) { 
    for (MyEnum me : MyEnum.values()) { 
     if (me.name().equalsIgnoreCase(str)) 
      return me; 
    } 
    return null; 
} 

Edit: Như ghi chú Jon Skeet, values() công trình bằng cách nhân bản một mảng ủng hộ tin mỗi khi nó được gọi. Nếu hiệu suất là quan trọng, bạn có thể muốn gọi values() chỉ một lần, bộ nhớ cache mảng, và lặp qua đó.

Ngoài ra, nếu enum của bạn có một số lượng lớn các giá trị, thay thế bản đồ của Jon Skeet có khả năng hoạt động tốt hơn bất kỳ lần lặp mảng nào.

4

Tôi không biết tại sao ai đó nói với bạn rằng bắt ngoại lệ thời gian chạy là xấu.

Sử dụng valueOf và bắt IllegalArgumentException là tốt để chuyển đổi/kiểm tra chuỗi thành enum.

+19

Không, IMO. Đó là thử nghiệm một tình huống không đặc biệt thông qua các ngoại lệ - sử dụng chúng để kiểm soát luồng trong điều kiện bình thường, không phải lỗi. Đó là việc sử dụng rất ít ngoại lệ IMO và một trong số đó có thể có tác động hiệu suất đáng kể. Ngoại lệ là tốt về hiệu suất bình thường, bởi vì chúng không nên xảy ra - nhưng khi bạn sử dụng chúng cho các điều kiện không phải lỗi, sau đó mã * trông * giống như nó sẽ chạy nhanh có thể bị sa lầy. –

+6

Điều thú vị - dựa trên ý kiến ​​này (mà tôi thường chia sẻ), tôi đã quyết định sử dụng phương thức "EnumUtils.isValidEnum' của Apache Commons để làm cho nó" sạch ". Và đoán làm thế nào 'EnumUtils.isValidEnum' được thực hiện - bắt' IllegalArgumentException' tất nhiên :-) –

+0

Giải pháp làm việc ngắn nhất. –

53

Nếu tôi cần làm điều này, đôi khi tôi xây dựng một tên là Set<String> hoặc thậm chí là của riêng mình Map<String,MyEnum> - thì bạn chỉ có thể kiểm tra điều đó.

Một vài điểm đáng chú ý:

  • dụng nhất bất kỳ bộ sưu tập tĩnh như vậy trong một initializer tĩnh. Không sử dụng trình khởi tạo biến và sau đó dựa vào nó đã được thực thi khi hàm tạo enum chạy - nó sẽ không được! (Các hàm khởi tạo enum là những thứ đầu tiên được thực hiện trước bộ khởi tạo tĩnh.)
  • Cố gắng tránh sử dụng values() thường xuyên - nó phải tạo và điền một mảng mới mỗi lần. Để lặp qua tất cả các phần tử, hãy sử dụng EnumSet.allOf hiệu quả hơn cho các enums mà không có nhiều yếu tố.

Mẫu mã:

import java.util.*; 

enum SampleEnum { 
    Foo, 
    Bar; 

    private static final Map<String, SampleEnum> nameToValueMap = 
     new HashMap<String, SampleEnum>(); 

    static { 
     for (SampleEnum value : EnumSet.allOf(SampleEnum.class)) { 
      nameToValueMap.put(value.name(), value); 
     } 
    } 

    public static SampleEnum forName(String name) { 
     return nameToValueMap.get(name); 
    } 
} 

public class Test { 
    public static void main(String [] args) 
     throws Exception { // Just for simplicity! 
     System.out.println(SampleEnum.forName("Foo")); 
     System.out.println(SampleEnum.forName("Bar")); 
     System.out.println(SampleEnum.forName("Baz")); 
    } 
} 

Tất nhiên, nếu bạn chỉ có một vài tên này có lẽ là quá mức cần thiết - một O (n), giải pháp thường thắng trên một O (1) giải pháp khi n là nhỏ đủ. Dưới đây là cách tiếp cận khác:

import java.util.*; 

enum SampleEnum { 
    Foo, 
    Bar; 

    // We know we'll never mutate this, so we can keep 
    // a local copy. 
    private static final SampleEnum[] copyOfValues = values(); 

    public static SampleEnum forName(String name) { 
     for (SampleEnum value : copyOfValues) { 
      if (value.name().equals(name)) { 
       return value; 
      } 
     } 
     return null; 
    } 
} 

public class Test { 
    public static void main(String [] args) 
     throws Exception { // Just for simplicity! 
     System.out.println(SampleEnum.forName("Foo")); 
     System.out.println(SampleEnum.forName("Bar")); 
     System.out.println(SampleEnum.forName("Baz")); 
    } 
} 
+0

'values ​​()' tạo và điền một mảng mới? Tôi không nhớ điều đó trước đây, nhưng có lẽ bạn có một nguồn? –

+7

Vâng nó trả về một mảng ... và nó không thể bảo vệ bạn khỏi việc đột biến mảng đó ... và nó sẽ không tiếp theo người gọi bị cản trở bởi điều đó. Ngoài ra, hãy nhìn vào các nguồn JRE :) –

+0

Nó không có trong các nguồn JRE, đó là lý do tại sao tôi hỏi. Nhưng tôi đã không nghĩ đến khía cạnh đột biến; có lẽ bạn đã đúng rồi. –

4

Dựa trên Jon Skeet câu trả lời tôi đã thực hiện một lớp cho phép làm điều đó một cách dễ dàng tại nơi làm việc:

import com.google.common.collect.ImmutableMap; 
import com.google.common.collect.Maps; 

import java.util.EnumSet; 
import java.util.HashSet; 
import java.util.Map; 
import java.util.Set; 

/** 
* <p> 
* This permits to easily implement a failsafe implementation of the enums's valueOf 
* Better use it inside the enum so that only one of this object instance exist for each enum... 
* (a cache could solve this if needed) 
* </p> 
* 
* <p> 
* Basic usage exemple on an enum class called MyEnum: 
* 
* private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class); 
* public static MyEnum failSafeValueOf(String enumName) { 
*  return FAIL_SAFE.valueOf(enumName); 
* } 
* 
* </p> 
* 
* <p> 
* You can also use it outside of the enum this way: 
* FailSafeValueOf.create(MyEnum.class).valueOf("EnumName"); 
* </p> 
* 
* @author Sebastien Lorber <i>([email protected])</i> 
*/ 
public class FailSafeValueOf<T extends Enum<T>> { 

    private final Map<String,T> nameToEnumMap; 

    private FailSafeValueOf(Class<T> enumClass) { 
     Map<String,T> map = Maps.newHashMap(); 
     for (T value : EnumSet.allOf(enumClass)) { 
      map.put(value.name() , value); 
     } 
     nameToEnumMap = ImmutableMap.copyOf(map); 
    } 

    /** 
    * Returns the value of the given enum element 
    * If the 
    * @param enumName 
    * @return 
    */ 
    public T valueOf(String enumName) { 
     return nameToEnumMap.get(enumName); 
    } 

    public static <U extends Enum<U>> FailSafeValueOf<U> create(Class<U> enumClass) { 
     return new FailSafeValueOf<U>(enumClass); 
    } 

} 

Và kiểm tra đơn vị:

import org.testng.annotations.Test; 

import static org.testng.Assert.*; 


/** 
* @author Sebastien Lorber <i>([email protected])</i> 
*/ 
public class FailSafeValueOfTest { 

    private enum MyEnum { 
     TOTO, 
     TATA, 
     ; 

     private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class); 
     public static MyEnum failSafeValueOf(String enumName) { 
      return FAIL_SAFE.valueOf(enumName); 
     } 
    } 

    @Test 
    public void testInEnum() { 
     assertNotNull(MyEnum.failSafeValueOf("TOTO")); 
     assertNotNull(MyEnum.failSafeValueOf("TATA")); 
     assertNull(MyEnum.failSafeValueOf("TITI")); 
    } 

    @Test 
    public void testInApp() { 
     assertNotNull(FailSafeValueOf.create(MyEnum.class).valueOf("TOTO")); 
     assertNotNull(FailSafeValueOf.create(MyEnum.class).valueOf("TATA")); 
     assertNull(FailSafeValueOf.create(MyEnum.class).valueOf("TITI")); 
    } 

} 

Lưu ý rằng tôi đã sử dụng ổi để tạo một bản đồ không thể thay đổi nhưng thực tế bạn có thể sử dụng một bản đồ bình thường mà tôi nghĩ vì bản đồ không bao giờ được trả lại ...

20

Một trong những lib yêu thích của tôi: Apache Commons.

EnumUtils có thể làm điều đó dễ dàng.

Dưới đây là một số mẫu mã để xác nhận một Enum:

MyEnum strTypeEnum = null; 

// test if String str is compatible with the enum 
if(EnumUtils.isValidEnum(MyEnum.class, str)){ 
    strTypeEnum = MyEnum.valueOf(str); 
} 
+0

Giải pháp tối thiểu tuyệt vời. – mumair

+0

Cảm ơn nhận xét của bạn. Thực tế nó là tối thiểu vì sử dụng libs khác, thường là giải pháp tốt nhất so với việc tự mã hóa lại. bạn đang đi đến cùng một giải pháp tương tự ("Tái phát minh bánh xe") hoặc bạn bỏ lỡ một cái gì đó quan trọng và nó hoạt động tồi tệ nhất (sau đó trong trường hợp đó bạn "Tái phát minh bánh xe vuông") –

+1

Có chính xác. bạn nhận được lợi ích đầy đủ hoặc tối đa từ nó :) Bánh xe vuông: P – mumair

1

Bạn cũng có thể sử dụng ổi và làm điều gì đó như thế này:

// This method returns enum for a given string if it exists, otherwise it returns default enum. 
private MyEnum getMyEnum(String enumName) { 
    // It is better to return default instance of enum instead of null 
    return hasMyEnum(enumName) ? MyEnum.valueOf(enumName) : MyEnum.DEFAULT; 
} 

// This method checks that enum for a given string exists. 
private boolean hasMyEnum(String enumName) { 
    return Iterables.any(Arrays.asList(MyEnum.values()), new Predicate<MyEnum>() { 
    public boolean apply(MyEnum myEnum) { 
     return myEnum.name().equals(enumName); 
    } 
    }); 
} 

Trong phương pháp thứ hai tôi sử dụng ổi (Google Guava) thư viện mà cung cấp lớp học Iterables rất hữu ích. Sử dụng phương thức Iterables.any() chúng ta có thể kiểm tra xem một giá trị đã cho có tồn tại trong một đối tượng danh sách hay không. Phương thức này cần hai tham số: một danh sách và Cung cấp đối tượng. Đầu tiên tôi sử dụng phương thức Arrays.asList() để tạo danh sách với tất cả các enums. Sau đó, tôi đã tạo đối tượng Predicate được sử dụng để kiểm tra xem phần tử đã cho (có trong trường hợp của chúng tôi) thỏa mãn điều kiện trong áp dụng phương thức hay không. Nếu điều đó xảy ra, phương pháp Iterables.any() trả về giá trị thực.

1

Hầu hết các câu trả lời đều đề xuất sử dụng vòng lặp bằng để kiểm tra xem enum có tồn tại hay sử dụng try/catch với enum.valueOf() hay không. Tôi muốn biết phương pháp nào nhanh hơn và thử nó. Tôi không giỏi điểm chuẩn, vì vậy hãy sửa tôi nếu tôi phạm sai lầm.

Heres mã của lớp học chính của tôi:

package enumtest; 

public class TestMain { 

    static long timeCatch, timeIterate; 
    static String checkFor; 
    static int corrects; 

    public static void main(String[] args) { 
     timeCatch = 0; 
     timeIterate = 0; 
     TestingEnum[] enumVals = TestingEnum.values(); 
     String[] testingStrings = new String[enumVals.length * 5]; 
     for (int j = 0; j < 10000; j++) { 
      for (int i = 0; i < testingStrings.length; i++) { 
       if (i % 5 == 0) { 
        testingStrings[i] = enumVals[i/5].toString(); 
       } else { 
        testingStrings[i] = "DOES_NOT_EXIST" + i; 
       } 
      } 

      for (String s : testingStrings) { 
       checkFor = s; 
       if (tryCatch()) { 
        ++corrects; 
       } 
       if (iterate()) { 
        ++corrects; 
       } 
      } 
     } 

     System.out.println(timeCatch/1000 + "us for try catch"); 
     System.out.println(timeIterate/1000 + "us for iterate"); 
     System.out.println(corrects); 
    } 

    static boolean tryCatch() { 
     long timeStart, timeEnd; 
     timeStart = System.nanoTime(); 
     try { 
      TestingEnum.valueOf(checkFor); 
      return true; 
     } catch (IllegalArgumentException e) { 
      return false; 
     } finally { 
      timeEnd = System.nanoTime(); 
      timeCatch += timeEnd - timeStart; 
     } 

    } 

    static boolean iterate() { 
     long timeStart, timeEnd; 
     timeStart = System.nanoTime(); 
     TestingEnum[] values = TestingEnum.values(); 
     for (TestingEnum v : values) { 
      if (v.toString().equals(checkFor)) { 
       timeEnd = System.nanoTime(); 
       timeIterate += timeEnd - timeStart; 
       return true; 
      } 
     } 
     timeEnd = System.nanoTime(); 
     timeIterate += timeEnd - timeStart; 
     return false; 
    } 
} 

Điều này có nghĩa, mỗi phương pháp chạy 50000 lần so với chiều dài của enum Tôi chạy thử nghiệm này nhiều lần, với 10, 20, 50 và 100 hằng enum . Đây là kết quả:

  • 10: try/catch: 760ms | lặp lại: 62ms
  • 20: try/catch: 1671ms | iteration: 177ms
  • 50: try/catch: 3113ms | lặp lại: 488ms
  • 100: try/catch: 6834ms | lặp lại: 1760ms

Kết quả này không chính xác. Khi thực hiện nó một lần nữa, có sự khác biệt lên đến 10% trong kết quả, nhưng chúng đủ để cho thấy rằng phương pháp try/catch kém hiệu quả hơn nhiều, đặc biệt là với các enums nhỏ.

Các vấn đề liên quan