Java枚举和泛型

Java enums and generics

这件事暂时让我不安。我以前问过问题,但可能用了一个不好的措词和一个太抽象的例子。所以不清楚我到底在问什么。我再试一次。请不要妄下结论。我想这个问题根本不容易回答!

为什么我不能用Java中的泛型类型参数枚举呢?

问题不在于它为什么不可能,句法上。我知道这是不受支持的。问题是:为什么JSR人员"忘记"或"忽略"了这个非常有用的特性?我无法想象与编译器相关的原因,为什么它不可行。

这就是我想做的。这在Java中是可能的。Java 1.4的方式创建类型安全枚举:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// A model class for SQL data types and their mapping to Java types
public class DataType<T> implements Serializable, Comparable<DataType<T>> {
    private final String name;
    private final Class<T> type;

    public static final DataType<Integer> INT      = new DataType<Integer>("int", Integer.class);
    public static final DataType<Integer> INT4     = new DataType<Integer>("int4", Integer.class);
    public static final DataType<Integer> INTEGER  = new DataType<Integer>("integer", Integer.class);
    public static final DataType<Long>    BIGINT   = new DataType<Long>   ("bigint", Long.class);    

    private DataType(String name, Class<T> type) {
        this.name = name;
        this.type = type;
    }

    // Returns T. I find this often very useful!
    public T parse(String string) throws Exception {
        // [...]
    }

    // Check this out. Advanced generics:
    public T[] parseArray(String string) throws Exception {
        // [...]
    }

    // Even more advanced:
    public DataType<T[]> getArrayType() {
        // [...]
    }

    // [ ... more methods ... ]
}

然后,您可以在许多其他地方使用

1
2
3
4
5
6
7
public class Utility {

    // Generic methods...
    public static <T> T doStuff(DataType<T> type) {
        // [...]
    }
}

但是这些事情对于枚举是不可能的:

1
2
3
4
5
6
7
8
9
// This can't be done
public enum DataType<T> {

    // Neither can this...
    INT<Integer>("int", Integer.class),
    INT4<Integer>("int4", Integer.class),

    // [...]
}

现在,如我所说。我知道这些东西就是这样设计的。enum是句法上的糖。仿制药也是。实际上,编译器完成了所有的工作,并将enums转换为java.lang.Enum的子类,将泛型转换为casts和合成方法。

但是为什么编译器不能进一步考虑通用枚举呢??

编辑:这就是我所期望的编译器生成的Java代码:

1
2
3
public class DataType<T> extends Enum<DataType<?>> {
    // [...]
}


我想猜测一下,这是因为枚举类本身的类型参数上的协方差问题,它被定义为Enum>,尽管调查这类的所有角情况有点困难。

除此之外,枚举的一个主要用例是使用诸如enumset和value of之类的东西,其中有一组具有不同泛型参数的东西,并从字符串中获取值,所有这些都不支持枚举本身的泛型参数,或者更糟。

我知道,当我试图用仿制药获得这种幻想时,我总是处于痛苦的世界中,我想象语言设计者窥视了那个深渊,决定不去那里,特别是因为这些特性是同时开发的,这意味着对事物的枚举方面的不确定性更大。

或者换一种方式,在处理那些本身具有泛型参数的类时,它会有Class的所有问题,并且您需要进行大量的转换和处理原始类型。对于您正在查看的用例类型,语言设计者认为不值得这样做。

edit:in response to the comments(and tom-a downvote?),嵌套的泛型参数会发生各种错误。枚举实现可比较。如果发挥了泛型的作用,在客户机代码中比较枚举的两个任意元素是行不通的。一旦你处理了一个通用参数的通用参数,你最终会遇到各种边界问题和头疼。很难设计一个处理好它的类。在可比较的情况下,如果不恢复为原始类型并获得编译器警告,我就无法找到一种方法使比较枚举的两个任意成员有效。你能?< /打击>

实际上,上面的错误是令人难堪的,因为我使用问题中的数据类型作为考虑这个问题的模板,但实际上枚举将具有子类,所以这并不完全正确。

然而,我坚持我的回答要点。汤姆提出了EnumSet.complementOf,当然,我们还有产生问题的valueOf,在某种程度上,枚举的设计可以起作用,我们必须认识到这是事后诸葛亮的事。枚举是与泛型同时设计的,没有验证所有这些角点案例的好处。特别是考虑到具有泛型参数的枚举的用例是相当有限的。(但同样,Enumset的用例也是如此)。


我认为不可能有遗传枚举。如果您可以入侵编译器,那么您可以拥有一个枚举的通用子类,并且您的通用枚举的类文件不会导致问题。

但归根结底,枚举在很大程度上是一种语法糖。在C、C++、C语言中,枚举基本上是int常数的别名。Java赋予了它更多的能量,但它仍然代表简单的项目。

在某个地方人们必须划出界线。仅仅因为类有枚举实例,并不意味着它必须是枚举。如果它在其他领域足够成熟,它应该是一个正规的班级。

在您的情况下,使DataType枚举没有太大的优势。你可以在交换的情况下使用枚举,这是很重要的。DataType的非枚举验证工作正常。


我认为您希望使用参数化枚举的原因归结为能够为枚举的各种常量使用不同的方法签名。

在您的示例中,parse的签名(参数类型和返回类型)为:

  • 对于Datatype.INTint parse(String)
  • 对于Datatype.VARCHARString parse(String)
  • 等等

那么,编译器如何能够对如下内容进行类型检查:

1
2
3
Datatype type = ...
...
int x = type.parse("45");

????

要对此类表达式应用静态类型和类型检查,方法的签名对于所有实例都必须相同。但是,最后您建议对不同的实例使用不同的方法签名…这就是为什么在Java中不可能做到这一点的原因。


我就是这么想的-

常规类有实例。您创建了一个类的新实例,出于某种目的使用它,然后释放它。例如,List是字符串列表。我可以用字符串做任何我想做的事情,然后当我完成后,我可以用整数做同样的功能。

对我来说,枚举器不是您创建实例的类型。这和单身汉一样。因此,我可以理解为什么Java不允许泛型为Enums,因为您不能创建一个新类型的枚举实例,就像使用类一样使用临时。枚举应该是静态的,并且全局只有一个实例。对我来说,允许全局只有一个实例的类使用泛型是没有意义的。

我希望这有帮助。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum GenericEnum<T> {
  SIMPLE, COMPLEX;

  public T parse(String s) {
    return T.parse(s);
  }
}

public void doSomething() {
  GenericEnum<Long> longGE = GenericEnum<Long>.SIMPLE;
  GenericEnum<Integer> intGE = GenericEnum<Integer>.SIMPLE;

  List<Long> longList = new LinkedList<Long>();
  List<Integer> intList = new LinkedList<Integer>();

  assert(longGE == intGE);              // 16
  assert(stringList.equals(intList));   // 17

  Object x = longGE.parse("1");  // 19
}

第16行和第17行的断言都是正确的。通用类型在运行时不可用。

枚举的一个优点是可以使用==来比较它们。第16行的断言将计算为true。

但是在19号线我们遇到了一个问题。longge和intge是同一个对象(正如第16行的断言所示)。解析("1")将返回什么?运行时通用类型信息不可用。所以在运行时无法确定parse方法的t。

枚举基本上是静态的,它们只存在一次。将泛型类型应用于静态类型是没有意义的。

我希望这有帮助。

注意-这不是工作代码。它使用了原始问题中建议的语法。