为什么JavaEnm文字不能具有泛型类型参数?

Why shouldn't Java enum literals be able to have generic type parameters?

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

1
2
3
4
5
public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

然后,这个通用类型参数可以在不同的地方使用。设想一个方法的泛型类型参数:

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

或者甚至在枚举类本身中:

1
public T convert(Object o);

更具体的例子1

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

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

更具体的例子2

我列举了数据类型:

1
2
3
4
5
6
7
8
9
10
11
public interface DataType<T> {}

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

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

问题:

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

谁能向我解释这个?在回答之前,请考虑:

  • 我知道通用类型被删除了:—)
  • 我知道有使用类对象的解决方法。他们是解决办法。
  • 如果适用,泛型类型将导致编译器生成的类型强制转换(例如,在调用convert()方法时)
  • 泛型类型将位于枚举上。因此,它受枚举的每个文本的约束。因此,编译器会知道在编写类似于String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);的内容时应用哪种类型。
  • 这同样适用于T getvalue()方法中的泛型类型参数。编译器可以在调用String string = someClass.getValue(LITERAL1)时应用类型转换。


目前正在讨论从jep-301增强型Enums开始。Jep中给出的例子,正是我所寻找的:

1
2
3
4
5
6
7
8
9
10
11
12
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

不幸的是,Jep仍在与重大问题作斗争:http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-may/000041.html


答案是:

because of type erasure

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}


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

MyEnum.valueOf(String name)呢?

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

public static MyEnum valueOf(String name);

为了像MyEnum myStringEnum = MyEnum.value("some string property")那样称呼它,这也行不通。例如,如果你称之为MyEnum myIntEnum = MyEnum.value("some string property")呢?实现该方法是不可能正确工作的,例如,由于类型擦除,当您像MyEnum.value("some double property")那样调用它时,抛出异常或返回空值。


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

更新:目前正在添加到jep 301下的语言:增强的枚举。


坦率地说,这似乎更像是寻找问题的解决方案。

JavaEnUM的全部目的是对以相似的属性共享相似类型属性的类型实例的枚举进行建模,该方法提供了可比字符串或整数表示的一致性和丰富性。

以文本书枚举为例。这不是非常有用或一致的:

1
2
3
4
5
6
public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

为什么我希望我的不同行星有不同的通用类型转换?它能解决什么问题?它是否有理由使语言语义复杂化?如果我真的需要这个行为是一个枚举实现它的最佳工具?

另外,您如何管理复杂的转换?

例如

1
2
3
4
public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

使用StringInteger很容易提供名称或顺序。但是泛型允许您提供任何类型的。您将如何管理到MyComplexClass的转换?现在,通过强制编译器知道可以提供给泛型枚举的类型的有限子集,以及对概念(泛型)引入额外的混淆(泛型),将两个构造搞得一团糟,这似乎已经被许多程序员所回避。


因为"enum"是枚举的缩写。它只是一组命名常量,用来代替序数,使代码更易于阅读。

我不知道类型参数化常量的预期含义是什么。


我想是因为基本上不能引用枚举

如果JVM允许,您将在哪里设置T类?

枚举是应该总是相同的数据,或者至少是不会发生一般性变化的数据。

新肌瘤< >

以下方法可能仍然有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }  
}