2012-07-15 34 views
42

Tôi có một giao diện chung như thế này:Làm thế nào để thực hiện enum với Generics?

interface A<T> { 
    T getValue(); 
} 

trường hợp giao diện này đã hạn chế, do đó nó sẽ là tốt nhất để thực hiện chúng như các giá trị enum. Vấn đề là những trường hợp có loại giá trị khác nhau, vì vậy tôi đã thử cách tiếp cận sau nhưng không biên dịch:

public enum B implements A { 
    A1<String> { 
     @Override 
     public String getValue() { 
      return "value"; 
     } 
    }, 
    A2<Integer> { 
     @Override 
     public Integer getValue() { 
      return 0; 
     } 
    }; 
} 

Bất kỳ ý tưởng nào về điều này?

Trả lời

42

Bạn không thể. Java không cho phép các kiểu generic trên hằng số enum. Họ được phép trên các loại enum, mặc dù:

public enum B implements A<String> { 
    A1, A2; 
} 

gì bạn có thể làm trong trường hợp này là một trong hai có một kiểu enum cho từng loại chung chung, hoặc 'giả' có một enum bằng cách chỉ làm cho nó một lớp:

public class B<T> implements A<T> { 
    public static final B<String> A1 = new B<String>(); 
    public static final B<Integer> A2 = new B<Integer>(); 
    private B() {}; 
} 

Thật không may, cả hai đều có nhược điểm.

+1

Cách tiếp cận này không giải quyết được vấn đề của tôi trừ khi tôi sử dụng 'enum B triển khai A ', từ đó làm cho giao diện chung vô nghĩa. –

+6

Đó là điểm tôi đang cố gắng thực hiện - vấn đề của bạn không thể giải quyết được bằng cách sử dụng một 'Enum'. – Jorn

+0

Bây giờ ít nhất tôi hiểu 'enum' không phù hợp với yêu cầu của tôi. Cảm ơn! –

24

Khi các nhà phát triển Java thiết kế các API nhất định, chúng tôi thường gặp phải vấn đề này. Tôi đã tái khẳng định nghi ngờ của riêng tôi khi tôi đi qua bài này, nhưng tôi có một workaround verbose với nó:

// class name is awful for this example, but it will make more sense if you 
// read further 
public interface MetaDataKey<T extends Serializable> extends Serializable 
{ 
    T getValue(); 
} 

public final class TypeSafeKeys 
{ 
    static enum StringKeys implements MetaDataKey<String> 
    { 
     A1("key1"); 

     private final String value; 

     StringKeys(String value) { this.value = value; } 

     @Override 
     public String getValue() { return value; } 
    } 

    static enum IntegerKeys implements MetaDataKey<Integer> 
    { 
     A2(0); 

     private final Integer value; 

     IntegerKeys (Integer value) { this.value = value; } 

     @Override 
     public Integer getValue() { return value; } 
    } 

    public static final MetaDataKey<String> A1 = StringKeys.A1; 
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2; 
} 

Vào thời điểm đó, bạn có được những lợi ích của một sự liên tục giá trị eration enum (và tất cả các các đặc quyền đi kèm với điều đó), cũng như triển khai độc đáo của interface, nhưng bạn có khả năng truy cập toàn cầu mong muốn theo số enum.

Rõ ràng, điều này làm tăng thêm độ dài, tạo ra khả năng xảy ra lỗi sao chép/dán. Bạn có thể tạo enum s public và chỉ cần thêm một lớp bổ sung vào quyền truy cập của họ.

Thiết kế có xu hướng sử dụng các tính năng này có xu hướng bị ảnh hưởng bởi một số giá trị duy nhất khác, chẳng hạn như tên, có thể trùng lặp vô tình trên cơ sở mã cho một mục đích tương tự nhưng khác nhau. Bằng cách sử dụng enum s trên bảng, bình đẳng là một freebie đó là miễn dịch với hành vi giòn như vậy.

Hạn chế lớn đối với hệ thống, vượt quá độ dài là ý tưởng chuyển đổi qua lại giữa các khóa duy nhất trên toàn cầu (ví dụ, marshaling đến và từ JSON). Nếu chúng chỉ là chìa khóa, thì chúng có thể được khôi phục một cách an toàn (được nhân bản) với chi phí lãng phí bộ nhớ, nhưng sử dụng trước đây là điểm yếu - equals - là một lợi thế.

Có một workaround này cung cấp tính độc đáo thực hiện toàn cầu bằng cách lộn xộn nó với một loại vô danh mỗi trường hợp toàn cầu:

public abstract class BasicMetaDataKey<T extends Serializable> 
    implements MetaDataKey<T> 
{ 
    private final T value; 

    public BasicMetaDataKey(T value) 
    { 
     this.value = value; 
    } 

    @Override 
    public T getValue() 
    { 
     return value; 
    } 

    // @Override equals 
    // @Override hashCode 
} 

public final class TypeSafeKeys 
{ 
    public static final MetaDataKey<String> A1 = 
     new BasicMetaDataKey<String>("value") {}; 
    public static final MetaDataKey<Integer> A2 = 
     new BasicMetaDataKey<Integer>(0) {}; 
} 

Lưu ý rằng mỗi trường hợp sử dụng một thực hiện nặc danh, nhưng không có gì khác là cần thiết để thực hiện nó , vì vậy, {} trống. Điều này gây nhầm lẫn và gây phiền nhiễu, nhưng nó hoạt động nếu các tham chiếu cá thể là thích hợp và lộn xộn được giữ ở mức tối thiểu, mặc dù nó có thể hơi khó hiểu đối với các nhà phát triển Java ít kinh nghiệm, do đó làm cho nó khó duy trì hơn.

Cuối cùng, cách duy nhất để cung cấp tính duy nhất toàn cầu và phân công lại là sáng tạo hơn một chút với những gì đang xảy ra.Việc sử dụng phổ biến nhất cho các giao diện chia sẻ trên toàn cầu mà tôi đã thấy là dành cho xô MetaData mà có xu hướng pha trộn rất nhiều giá trị khác nhau, với các loại khác nhau (T, trên một cơ sở cho mỗi key):

public interface MetaDataKey<T extends Serializable> extends Serializable 
{ 
    Class<T> getType(); 
    String getName(); 
} 

public final class TypeSafeKeys 
{ 
    public static enum StringKeys implements MetaDataKey<String> 
    { 
     A1; 

     @Override 
     public Class<String> getType() { return String.class; } 

     @Override 
     public String getName() 
     { 
      return getDeclaringClass().getName() + "." + name(); 
     } 
    } 

    public static enum IntegerKeys implements MetaDataKey<Integer> 
    { 
     A2; 

     @Override 
     public Class<Integer> getType() { return Integer.class; } 

     @Override 
     public String getName() 
     { 
      return getDeclaringClass().getName() + "." + name(); 
     } 
    } 

    public static final MetaDataKey<String> A1 = StringKeys.A1; 
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2; 
} 

này cung cấp tính linh hoạt tương tự như tùy chọn đầu tiên, và nó cung cấp một cơ chế để có được một tham chiếu thông qua sự phản chiếu, nếu nó trở nên cần thiết sau này, do đó tránh sự cần thiết phải nhanh chóng sau này. Nó cũng tránh được rất nhiều lỗi dễ bị sao chép/dán lỗi mà tùy chọn đầu tiên cung cấp vì nó sẽ không biên dịch nếu phương thức đầu tiên là sai, và phương thức thứ hai không cần phải thay đổi. Lưu ý duy nhất là bạn nên đảm bảo rằng các enum s có nghĩa là để được sử dụng trong thời trang đó là public để tránh bất kỳ ai gặp phải lỗi truy cập vì họ không có quyền truy cập vào bên trong enum; nếu bạn không muốn có những đường dây MetaDataKey s đi ngang qua một dây marshaled, thì việc giữ chúng ẩn khỏi các gói bên ngoài có thể được sử dụng để tự động loại bỏ chúng (trong khi marshaling, kiểm tra một cách phản ánh để xem liệu enum có thể truy cập được không, và nếu không, sau đó bỏ qua khóa/giá trị). Không có gì đạt được hoặc bị mất bằng cách làm cho nó public ngoại trừ việc cung cấp hai cách để truy cập cá thể, nếu tài liệu tham khảo static rõ ràng hơn được duy trì (vì trường hợp enum chỉ là như vậy).

Tôi chỉ ước rằng họ đã thực hiện để enum có thể mở rộng các đối tượng trong Java. Có lẽ trong Java 9?

Tùy chọn cuối cùng không thực sự giải quyết nhu cầu của bạn, vì bạn đang yêu cầu các giá trị, nhưng tôi nghi ngờ rằng điều này hướng tới mục tiêu thực tế.

+0

Cảm ơn rất nhiều vì đã chia sẻ cách giải quyết này! Trên thực tế, bạn không cần phải sử dụng 'enum', chỉ cần sử dụng các lớp bên trong vô danh, đó là những gì tôi đang làm bây giờ. –

+1

Không sao cả. Lớp bên trong vô danh là ví dụ ở giữa. Tôi có xu hướng tránh các lớp ẩn danh để bảo trì (cả hai mã được biên dịch trở nên khó hiểu với các kiểu ẩn danh '$ 1',' $ 2' và nhiều nhà phát triển sẽ bỏ lỡ lý do là loại ẩn danh), nhưng nó chắc chắn là một cách tiếp cận hợp lệ và nhỏ hơn. – pickypg

+0

Tôi thực sự đánh giá cao câu trả lời này. Cảm ơn bạn đã nỗ lực của bạn. –

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