72

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

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

对此有任何想法吗?

4

4 回答 4

69

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

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

于 2012-07-15T08:24:34.187 回答
40

作为设计某些 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;
}

到那时,您将获得真正恒定的enumeration 值(以及随之interface而来的所有好处)的好处,以及成为enum.

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

倾向于使用这些功能的设计往往会遭受脆弱的equals实现,因为它们通常与其他一些独特的值相结合,例如名称,它可能会在代码库中出于相似但不同的目的而不知不觉地复制。通过全面使用enums,平等是对这种脆弱行为免疫的免费赠品。

除了冗长之外,此类系统的主要缺点是在全局唯一键之间来回转换的想法(例如,与 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这种方式使用的 s 是public为了避免任何人因为无法访问内部而出现访问错误enum;如果您不想让那些MetaDataKeys 穿过编组的电线,那么可以使用将它们隐藏在外部包裹之外来自动丢弃它们(在编组期间,反射性地检查是否enum是可访问的,如果不是,则忽略键/值)。如果维护更明显的引用(因为实例就是这样) ,那么public除了提供两种访问实例的方法外,没有任何收获或损失。staticenum

我只是希望他们能做到,以便enums 可以在 Java 中扩展对象。也许在 Java 9 中?

最后一个选项并不能真正解决您的需求,因为您要求价值,但我怀疑这会达到实际目标。

于 2013-05-15T05:53:13.313 回答
17

如果 JEP 301:Enhanced Enums被接受,那么您将能够使用这样的语法(取自提案):

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;
    }
}
于 2018-07-23T13:25:35.133 回答
1

通过使用这个 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();
}
于 2021-05-25T16:20:37.073 回答