如何用泛型实现枚举?

Zha*_* Yi 57 java generics enums interface

我有这样的通用接口:

interface A<T> {
    T getValue();
}
Run Code Online (Sandbox Code Playgroud)

此接口具有有限的实例,因此最好将它们实现为枚举值.问题是那些实例有不同类型的值,所以我尝试了以下方法,但它不编译:

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

对此有何想法?

Jor*_*orn 56

你不能.Java不允许在枚举常量上使用泛型类型.但它们在枚举类型上是允许的:

public enum B implements A<String> {
  A1, A2;
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下你可以做的是要么为每个泛型类型都有一个枚举类型,要么只是让它成为一个类的'假'有一个枚举:

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() {};
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这就是我想要做的一点 - 使用单个"Enum"无法解决您的问题. (7认同)
  • 这种方法不能解决我的问题,除非我使用 `enum B 实现 A&lt;Object&gt;`,这反过来又使通用接口变得毫无意义。 (2认同)

pic*_*ypg 33

作为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;
}
Run Code Online (Sandbox Code Playgroud)

在这一点上,你可以获得真正恒定的enumeration值(以及与之相关的所有特权),以及它的独特实现interface,但是你拥有所需的全局可访问性enum.

显然,这会增加冗长,从而产生复制/粘贴错误的可能性.您可以制作enums public并简单地为其访问添加一个额外的图层.

倾向于使用这些功能的设计往往会受到脆弱的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) {};
}
Run Code Online (Sandbox Code Playgroud)

请注意,每个实例都使用匿名实现,但实现它不需要任何其他实现,因此它们{}是空的.这既令人困惑又烦人,但是如果实例引用更可取并且杂乱保持在最低限度,它就可以工作,尽管对于经验不足的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;
}
Run Code Online (Sandbox Code Playgroud)

这提供了与第一个选项相同的灵活性,并且它提供了一种通过反射获得引用的机制,如果以后需要它,则因此避免以后需要实例化.它还避免了第一个选项提供的许多容易出错的复制/粘贴错误,因为如果第一个方法错误则不会编译,第二个方法不需要更改.唯一需要注意的是,您应该确保enum以这种方式使用的s是public为了避免任何人因为无法访问内部而导致访问错误enum; 如果你不想MetaDataKey让它们穿过编组的电线,那么将它们从外部包装中隐藏起来可以用来自动丢弃它们(在编组期间,反射性地检查是否enum可以访问,如果不是,则忽略键/值).public除了提供两种访问实例的方法之外,没有任何获得或丢失的东西,如果static维护更明显的引用(因为enum实例只是那样).

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

最终选项并没有真正解决您的需求,因为您要求的是价值观,但我怀疑这是实现目标的.

  • 没问题。匿名内部类是中间的例子。为了可维护性,我倾向于避免匿名类(编译后的代码都会与“$1”、“$2”匿名类型混淆,许多开发人员会错过成为匿名类型的原因),但这绝对是一种有效且较小的方法。 (2认同)

Sle*_*led 10

如果 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,它实际上已被撤回:http://openjdk.java.net/jeps/301 (6认同)
  • @TiStrga 在 2020 年 9 月 29 日他们写道“我们现在正在撤回这个 JEP”,您提到的 12 月电子邮件来自 2018 年 12 月。这个 JEP 显然已经死了。 (3认同)
  • 我读错了!我已经删除了误导性链接;谢谢。遗憾的是,这种语言功能本来可以清理大量代码。 (3认同)