2012-07-15 271 views
42

我有一个这样的通用接口:如何用泛型实现枚举?

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

此接口已经有限的情况下,因此这将是最好的实现他们为枚举值。问题是这些实例有不同类型的值,所以我尝试了以下方法,但它不能编译:

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

对此有何想法?

回答

42

你不行。 Java不允许在枚举常量上使用泛型类型。他们被允许在枚举类型,但:

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

,你可以在这种情况下,做的是要么为每个泛型类型枚举类型,或只是使其成为一类“假”有一个枚举:

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() {}; 
} 

不幸的是,他们都有缺点。

+1

这种方法不解决我的问题。 – 2012-07-15 08:28:49

+6

这就是我试图做的 - 你的问题不能通过使用一个'Enum'来解决。 – Jorn 2012-07-15 08:34:45

+0

现在至少我明白'枚举'不适合我的要求。谢谢! – 2012-07-15 08:39:24

24

作为Java开发人员设计某些API,我们遇到这个问题频繁。我再次确认我自己的疑惑,当我遇到这个帖子来了,但我有一个详细的解决方法吧:

// 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; 
} 

在这一点上,你获得的是一个真正的恒enum关合作价值的利益(和所有的与此相伴的福利),以及interface的独特实现,但您拥有enum所需的全局可访问性。

显然,这增加了冗长,这创造了复制/粘贴错误的可能性。您可以制作enum s public并简单地为其访问添加一个额外的图层。

倾向于使用这些功能的设计往往会遭受脆弱的实现,因为它们通常会与其他一些独特的值(如名称)耦合在一起,这些值可能无意中在代码库中重复出现,以实现类似但不同的目的。通过全面使用enum,平等是免费的免费的脆弱行为。

的主要缺点,例如系统,超越冗长,是来回转换全局唯一键之间(例如,编组和从JSON)的想法。如果他们只是钥匙,那么他们可以以浪费内存为代价安全地重新实施(重复),但使用之前的弱点 - 优势在于 - equals

有一种变通方法,以这种通过与每全局实例匿名类型塞满它提供了全球范围内实施的独特性:

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) {}; 
} 

注意,每个实例使用匿名执行,但没有别的,是落实它需要,所以{}是空的。这既令人困惑又令人讨厌,但它适用于实例引用更可取并且杂乱度保持最低的情况,尽管对于经验较少的Java开发人员来说可能有点神秘,因此难以维护。

最后,提供全球唯一性和重新分配的唯一方法是对所发生的事情稍微有点创意。对于全球共享接口,最常见的用法,我已经看到是倾向于混合了很多不同的价值观,不同类型的元数据桶(在T,在每个键基础上):

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; 
} 

这提供了与第一种选择相同的灵活性,并且它提供了一种通过反射获得参考的机制(如果稍后有必要的话),因此避免了稍后需要实例化。它还避免了第一个选项提供的大量容易出错的复制/粘贴错误,因为如果第一个方法错误,它将不会编译,而第二个方法不需要更改。唯一要注意的是,你应该确保enum命中注定该方式使用是public避免让任何人访问错误,因为他们没有进入内enum;如果你不想拥有这些MetaDataKey回事跨越编组线,然后让他们隐藏的来自外部的包可用于自动丢弃(编组时,反射性地检查,看看是否enum是可访问的,如果不是,然后忽略键/值)。除非提供两种访问实例的方法,否则没有任何获取或丢失,除非提供了更明显的static引用(因为无论如何,enum实例就是这样)。

我只是希望他们把它使enum小号可以用Java扩展对象。也许在Java 9中?

最后的选择并不能真正解决您的需求,为你问的价值,但我怀疑这对获取的实际目标。除非我用`枚举乙implemts一个',这反过来又使通用接口无意义

  0

非常感谢分享此解决方法!其实你不需要使用'enum',只需使用匿名内部类,这正是我现在所做的。Zhao Yi 22 5月. 13

+1

没问题。匿名的内部类是中间的例子。我倾向于避免使用匿名类来维护可维护性(编译后的代码变得与'$ 1','2'类匿名类型混淆,许多开发人员会错过匿名类型的原因),但这绝对是一种有效的和更小的方法。pickypg 22 5月. 13

  0

我真的很感激这个答案。感谢你的付出。Christian Conti-Vock 01 11月. 17

  0

这是一篇旧帖子,但您能否总结您提供的实施的成本和收益?我很难理解为什么我会选择其中一个。Matthew Phipps 16 1月. 18


+0

非常感谢分享此解决方法!其实你不需要使用'enum',只需使用匿名内部类,这正是我现在所做的。 – 2013-05-22 06:12:07

+1

没问题。匿名的内部类是中间的例子。我倾向于避免使用匿名类来维护可维护性(编译后的代码变得与'$ 1','2'类匿名类型混淆,许多开发人员会错过匿名类型的原因),但这绝对是一种有效的和更小的方法。 – pickypg 2013-05-22 06:42:12

+0

我真的很感激这个答案。感谢你的付出。 – 2017-11-01 16:22:13