有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

为什么Java枚举文本不能具有泛型类型参数?

Java枚举非常好。泛型也是如此。当然,由于类型擦除,我们都知道后者的局限性。但有一件事我不明白,为什么我不能创建这样的枚举:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

这个泛型类型参数<T>在不同的地方可能会有用。想象一个方法的泛型类型参数:

public <T> T getValue(MyEnum<T> param);

甚至在枚举类本身中:

public T convert(Object o);

更具体的例子#1

因为上面的例子对某些人来说可能太抽象了,这里有一个更真实的例子来说明我为什么要这么做。在这个例子中,我想使用

  • 枚举,因为这样我可以枚举一组有限的属性键
  • 泛型,因为这样我就可以有方法级别的类型安全性来存储属性
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

更具体的例子#2

我有一个数据类型的枚举:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

每个枚举文字显然都有基于泛型类型<T>的附加属性,同时也是一个枚举(不可变、单例、可枚举等)

问题:

没人想到这个吗?这是与编译器相关的限制吗?考虑到关键字“enum”是作为语法糖实现的,代表JVM生成的代码,我不理解这个限制

谁能给我解释一下?在回答之前,考虑一下:

  • 我知道泛型类型被删除:-)
  • 我知道有一些使用类对象的变通方法。它们是变通办法
  • 泛型类型在适用的情况下会导致编译器生成类型强制转换(例如,在调用convert()方法时)
  • 泛型<;T>;将在枚举中。因此,它由每个枚举的文本绑定。因此,编译器将知道在编写类似String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);的内容时应用哪种类型
  • 这同样适用于T getvalue()方法中的泛型类型参数。编译器可以在调用String string = someClass.getValue(LITERAL1)时应用类型转换

共 (6) 个答案

  1. # 1 楼答案

    令人遗憾的是,这一点在JEP-301 Enhanced Enums时已经讨论过,后来被撤回。JEP中给出的示例正是我想要的:

    enum Argument<X> { // declares generic enum
       STRING<String>(String.class), 
       INTEGER<Integer>(Integer.class), ... ;
    
       Class<X> clazz;
    
       Argument(Class<X> clazz) { this.clazz = clazz; }
    
       Class<X> getClazz() { return clazz; }
    }
    
    Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant
    

    不幸的是,正义与平等党正在与重大问题斗争,这些问题无法解决:http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html

  2. # 2 楼答案

    因为你不能,说真的。这可以添加到语言规范中,但还没有。这会增加一些复杂性。这种成本效益比意味着它不是一个优先考虑的问题

    更新:当前正在添加到JEP 301: Enhanced Enums下的语言中

  3. # 3 楼答案

    问题的答案是:

    because of type erasure

    这两种方法都不可能,因为参数类型已被擦除

    public <T> T getValue(MyEnum<T> param);
    public T convert(Object);
    

    为了实现这些方法,您可以将枚举构造为:

    public enum MyEnum {
        LITERAL1(String.class),
        LITERAL2(Integer.class),
        LITERAL3(Object.class);
    
        private Class<?> clazz;
    
        private MyEnum(Class<?> clazz) {
          this.clazz = clazz;
        }
    
        ...
    
    }
    
  4. # 4 楼答案

    枚举中还有其他方法不起作用。MyEnum.values()会返回什么

    MyEnum.valueOf(String name)

    如果您认为编译器可以使泛型方法

    public static MyEnum valueOf(String name);

    为了像MyEnum<String> myStringEnum = MyEnum.value("some string property")那样称呼它,这也行不通。 例如,如果调用MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property"),会怎么样? 不可能实现该方法来正确工作,例如,由于类型擦除,当您像MyEnum.<Int>value("some double property")一样调用它时,抛出异常或返回null

  5. # 5 楼答案

    通过使用这个Java注释处理器https://github.com/cmoine/generic-enums,您可以编写如下内容(convert方法作为示例实现):

    import org.cmoine.genericEnums.GenericEnum;
    import org.cmoine.genericEnums.GenericEnumParam;
    
    @GenericEnum
    public enum MyEnum {
        LITERAL1(String.class) {
            @Override
            @GenericEnumParam
            public Object convert(Object o) {
                return o.toString(); // example
            }
        },
        LITERAL2(Integer.class) {
            @Override
            @GenericEnumParam
            public Object convert(Object o) {
                return o.hashCode(); // example
            }
        },
        LITERAL3(Object.class) {
            @Override
            @GenericEnumParam
            public Object convert(Object o) {
                return o; // example
            }
        };
    
        MyEnum(Class<?> clazz) {
        }
    
        @GenericEnumParam
        public abstract Object convert(Object o);
    }
    

    注释处理器将生成一个枚举MyEnumExt(可自定义),它克服了java枚举的限制。相反,它生成了一个完全可用作枚举的Java类(最后,一个枚举被编译成一个实现Enum的Java类)

  6. # 6 楼答案

    坦率地说,这似乎更像是寻找问题的解决方案,而不是任何东西

    java枚举的全部目的是对共享相似属性的类型实例的枚举进行建模,其提供的一致性和丰富性超出了可比较的字符串或整数表示

    以教科书枚举为例。这不是非常有用或一致:

    public enum Planet<T>{
        Earth<Planet>,
        Venus<String>,
        Mars<Long>
        ...etc.
    }
    

    为什么我希望我的不同行星有不同的泛型类型转换?它解决了什么问题?这是否证明了语言语义的复杂性?如果我真的需要这个行为,枚举是实现它的最好工具吗

    此外,您将如何管理复杂的转换

    比如说

    public enum BadIdea<T>{
       INSTANCE1<Long>,
       INSTANCE2<MyComplexClass>;
    }
    

    String{}很容易提供名称或序号。但是泛型可以让你提供任何类型。您将如何管理到MyComplexClass的转换?现在,通过强制编译器知道可以提供给泛型枚举的类型的有限子集,并对概念(泛型)引入额外的混淆(泛型),这似乎已经让许多程序员无法理解,从而破坏了两个构造