如何用泛型实现枚举?

我有一个这样的通用接口:

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

你知道这是怎么回事吗?

98644 次浏览

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

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

作为设计某些 API 的 Java 开发人员,我们经常遇到这个问题。当我看到这篇文章的时候,我正在重新确认我自己的怀疑,但是我有一个冗长的解决办法:

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

在这一点上,您获得了一个真正恒定的 enumation 值(以及与之相关的所有特权) ,以及 interface的独特实现的好处,但是您拥有 enum所期望的全局可访问性。

显然,这增加了冗长性,从而可能导致复制/粘贴错误。您可以使 enumspublic,并简单地添加一个额外的层,以他们的访问。

倾向于使用这些特性的设计往往容易受到脆弱的 equals实现的影响,因为它们通常与一些其他独特的价值相结合,例如一个名称,这些价值可能会被不知不觉地在整个代码库中复制,以达到类似但不同的目的。通过全面使用 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 开发人员来说,它可能有点神秘,因此更难维护。

最后,提供全局唯一性和重新分配的唯一方法是对正在发生的事情更有一点创造性。我所见过的全局共享接口最常见的用法是 MetaData 存储桶,它往往混合了许多不同的值和不同的类型(每个键基础上的 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;
}

这提供了与第一个选项相同的灵活性,并提供了一种通过反射获取引用的机制(如果以后需要的话) ,从而避免了以后对实例化的需要。它还避免了第一个选项提供的许多易出错的复制/粘贴错误,因为如果第一个方法出错,它就不会编译,而第二个方法不需要更改。唯一需要注意的是,你应该确保以这种方式使用的 enumpublic,以避免任何人得到访问错误,因为他们没有访问内部 enum; 如果你不想让这些 MetaDataKey通过编组线路,那么保持隐藏的外部包可以用来自动丢弃它们(在编组期间,反射性地检查 enum是否可访问,如果不是,那么忽略键/值)。如果维护了更明显的 static引用(因为 enum实例就是这样) ,那么使其成为 public除了提供两种访问实例的方法之外,没有任何得失。

我只是希望他们能让 enum在 Java 中扩展对象,也许在 Java9中?

最后一个选项并不能真正解决您的需求,因为您要求的是价值,但是我怀疑这会朝着实际目标的方向发展。

如果 JEP 301: 增强枚举被接受,那么你将能够使用这样的语法(取自提案) :

enum Primitive<X> {
INT<Integer>(Integer.class, 0) {
int mod(int x, int y) { return x % y; }
int add(int x, int y) { return x + y; }
},
FLOAT<Float>(Float.class, 0f)  {
long add(long x, long y) { return x + y; }
}, ... ;


final Class<X> boxClass;
final X defaultValue;


Primitive(Class<X> boxClass, X defaultValue) {
this.boxClass = boxClass;
this.defaultValue = defaultValue;
}
}

通过使用这个 Java 注释处理器 https://github.com/cmoine/generic-enums,您可以编写以下代码:

import org.cmoine.genericEnums.GenericEnum;
import org.cmoine.genericEnums.GenericEnumParam;


@GenericEnum
public enum B implements A<@GenericEnumParam Object> {
A1(String.class, "value"), A2(int.class, 0);


@GenericEnumParam
private final Object value;


B(Class<?> clazz, @GenericEnumParam Object value) {
this.value = value;
}


@GenericEnumParam
@Override
public Object getValue() {
return value;
}
}

注释处理器将生成一个枚举 BExt,希望能满足您的所有需求!

如果你愿意,你也可以使用这种语法:

import org.cmoine.genericEnums.GenericEnum;
import org.cmoine.genericEnums.GenericEnumParam;


@GenericEnum
public enum B implements A<@GenericEnumParam Object> {
A1(String.class) {
@Override
public @GenericEnumParam Object getValue() {
return "value";
}
}, A2(int.class) {
@Override
public @GenericEnumParam Object getValue() {
return 0;
}
};


B(Class<?> clazz) {
}


@Override
public abstract @GenericEnumParam Object getValue();
}