如何用generics实现枚举?
我有一个像这样的通用接口:
interface A<T> { T getValue(); }
这个接口具有有限的实例,因此最好将它们作为枚举值来实现。 问题是那些实例有不同types的值,所以我尝试了下面的方法,但它不能编译:
public enum B implements A { A1<String> { @Override public String getValue() { return "value"; } }, A2<Integer> { @Override public Integer getValue() { return 0; } }; }
任何想法呢?
你不能。 Java不允许generics常量的genericstypes。 虽然它们允许使用枚举types:
public enum B implements A<String> { A1, A2; }
在这种情况下,你可以做的是对每个genericstypes都有一个枚举types,或者把它作为一个类来“枚举”一个枚举types:
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() {}; }
不幸的是,他们都有缺点。
作为devise某些API的Java开发人员,我们经常遇到这个问题。 当我遇到这个post时,我重新确认了自己的疑惑,但是我有一个详细的解决方法:
// 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
public
并简单地添加一个额外的层到他们的访问。
倾向于使用这些特性的devise往往会遭受脆弱的equals
实现,因为它们通常会与其他一些独特的值(如名称)耦合在一起,这些名称可能无意中在代码库中重复出现,以实现类似但不同的目的。 通过全面使用enum
,平等是免费的免费这种脆弱的行为。
诸如系统之外的主要缺点是,在全局唯一键之间来回转换的想法(例如,来自和来自JSON的封送处理)。 如果他们只是钥匙,那么他们可以以浪费记忆为代价安全地重新实施(重复),但是使用之前的弱点 – equals
– 是一个优势。
有一个解决方法,通过使用每个全局实例的匿名types混淆它来提供全局实现唯一性:
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开发人员来说,这可能有点神秘,因此难以维护。
最后,提供全球唯一性和重新分配的唯一方法就是对所发生的事情更有创意。 我所见过的全球共享接口最常见的用途是MetaData桶,它们倾向于混合使用不同types的许多不同的值( 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; }
这提供了与第一个选项相同的灵活性,并且它提供了一种机制,用于通过reflection来获取引用,如果稍后有必要的话,则可以避免以后需要实例化。 它也避免了第一个选项提供的很多容易出错的复制/粘贴错误,因为如果第一个方法是错误的,它不会编译,第二个方法不需要改变。 唯一值得注意的是,你应该确保这样使用的enum
是public
以避免任何人因访问内部enum
而无法访问错误; 如果你不想让这些MetaDataKey
通过编组线,那么将它们隐藏在外面的包可以被用来自动丢弃它们(在编组过程中,反思性地检查是否可以访问enum
,如果不是,然后忽略键/值)。 除了提供两种访问实例的方法,如果维护更明显的static
引用(因为enum
实例正是这样),没有任何东西被获取或丢失。
我只是希望他们这样做,以便enum
可以扩展Java中的对象。 也许在Java 9中?
最后的select并不能真正解决你的需求,因为你是在寻求价值,但是我怀疑这是实际的目标。