Java 枚举和泛型

发布于 2024-10-19 15:00:42 字数 2424 浏览 1 评论 0 原文

这件事现在困扰我一段时间了。我问了问题以前,但可能措辞不好,例子也太抽象。所以不清楚我实际上在问什么。我会再试一次。请不要急于下结论。我想这个问题并不容易回答!

为什么我不能在 Java 中使用带有泛型类型参数的枚举?

问题不在于为什么它在语法上不可能。我知道它只是不受支持。问题是:为什么JSR人们“忘记”或“省略”了这个非常有用的功能?我无法想象与编译器相关的原因,为什么它不可行。

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

// 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 ... ]
}

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

public class Utility {

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

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

// This can't be done
public enum DataType<T> {

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

    // [...]
}

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

但是为什么编译器不能更进一步并允许通用枚举?

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

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

This thing is troubling me for a while now. I have asked questions before, but probably with a bad phrasing and an example that was too abstract. So it wasn't clear what I was actually asking. I'll try again. And please don't jump to conclusions. I expect that the question is not easy at all to answer!

why can't I have an enum with generic type parameters in Java?

The question is not about why it's not possible, syntactically. I know it's just not supported. The question is: why did the JSR people "forget" or "omit" this very useful feature? I can't imagine a compiler-related reason, why it wouldn't be feasible.

Here's what I would love to do. This is possible in Java. It's the Java 1.4 way to create typesafe enums:

// 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 ... ]
}

And then, you could use <T> in many other places

public class Utility {

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

But these things are not possible with an enum:

// This can't be done
public enum DataType<T> {

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

    // [...]
}

Now, as I said. I know these things have been designed exactly that way. enum is syntactic sugar. So are generics. Actually, the compiler does all the work and transforms enums into subclasses of java.lang.Enum and generics into casts and synthetic methods.

but why can't the compiler go further and allow for generic enums??

EDIT:
This is what I would expect as compiler-generated Java code:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

不爱素颜 2024-10-26 15:00:42

我会猜测一下,这是因为 Enum 类本身的类型参数的协方差问题,它被定义为 Enum>,尽管调查所有的极端情况有点太多了。

除此之外,枚举的主要用例是像 EnumSet 和 valueOf 这样的东西,你有一个具有不同泛型参数的集合,并从字符串中获取值,所有这些都不会支持或更糟枚举本身的泛型参数。

我知道,当我尝试使用泛型时,我总是处于痛苦的世界中,我想象语言设计者看到了那个深渊,并决定不去那里,特别是因为这些功能是同时开发的,这意味着甚至Enum 方面的不确定性更大。

或者换句话说,在处理本身具有泛型参数的类时,它会遇到 Class 的所有问题,并且您必须进行大量转换和处理原始类型。语言设计者认为对于您正在查看的用例类型而言,这并不是真正值得的事情。

编辑:为了回应评论(和汤姆 - 否决票?),嵌套通用参数会导致各种不好的事情发生。 Enum 实现了 Comparable。如果使用泛型,则根本无法比较客户端代码中枚举的两个任意元素。一旦处理了泛型参数的泛型参数,您最终会遇到各种边界问题和令人头痛的问题。设计一个能够很好地处理它的类是很困难的。在可比较的情况下,我无法找到一种方法来使其可以比较枚举的两个任意成员,而无需恢复为原始类型并获得编译器警告。可以吗?

实际上,上面的内容是令人尴尬的错误,因为我使用问题中的 DataType 作为思考这个问题的模板,但实际上 Enum 会有一个子类,所以这不太正确。

不过,我坚持我的回答的要点。 Tom 提出了 EnumSet.complementOf,当然我们仍然有 valueOf 会产生问题,并且就 Enum 的设计可能起作用的程度而言,我们必须意识到这是一个事后诸葛亮的事情。枚举是与泛型同时设计的,并且没有验证所有此类极端情况的好处。特别是考虑到具有通用参数的 Enum 的用例相当有限。 (但话又说回来,EnumSet 的用例也是如此)。

I'm going to guess a bit and say that it is because of covariance issues on the type parameter of the Enum class itself, which is defined as Enum<E extends Enum<E>>, although it is a bit much to investigate all the corner cases of that.

Besides that, a primary use case of enums is with things like EnumSet and valueOf where you have a collection of things with different generic parameters and get the value from a string, all of which would not support or worse the generic parameter on the enum itself.

I know I'm always in a world of pain when I try to get that fancy with Generics, and I imagine the language designers peeked at that abyss and decided to not go there, especially since the features were developed concurrently, which would mean even more uncertainty for the Enum side of things.

Or put another way, it would have all the problems of Class<T> in dealing with classes which themselves have generic parameters, and you would have to do a lot of casting and dealing with raw types. Not truly something that the language designers felt was worth it for the type of use case you are looking at.

EDIT: In response to the comments (and Tom - a downvote?), nested generic parameter makes all kinds of bad things happen. Enum implements Comparable. That simply would not work to compare two arbitrary elements of the enum in client code if generics were in play. Once you deal with a Generic parameter of a Generic parameter, you end up with all kinds of bounds problems and headaches. It is hard to design a class that handles it well. In the case of comparable, I could not figure out a way to make it work to compare two arbitrary members of an enum without reverting to raw types and getting a compiler warning. Could you?

Actually the above is embarrassingly wrong, as I was using the DataType in the question as my template for thinking about this, but in fact an Enum would have a subclass, so that isn't quite right.

However, I stand by the gist of my answer. Tom brought up EnumSet.complementOf and of course we still have valueOf that produces problems, and to the degree that the design of Enum could have worked, we have to realize that that is a 20/20 hindsight thing. Enum was being designed concurrently with generics and didn't have the benefit of validating all such corner cases. Especially considering that the use case for an Enum with a generic parameter is rather limited. (But then again, so is the use case for EnumSet).

乖不如嘢 2024-10-26 15:00:42

我不认为泛化枚举是不可能的。如果您可以侵入编译器,则可以拥有通用的 Enum 子类,并且通用枚举的类文件不会导致问题。

但最终,枚举几乎是一种语法糖。在 C、C++、C# 中,枚举基本上是 int 常量的别名。 Java 赋予了它更多的功能,但它仍然应该来表示简单的项目。

人们必须在某个地方划清界限。仅仅因为一个类具有枚举实例,并不意味着它必须是一个枚举。如果它在其他方面足够复杂,那么它就应该成为一个普通的课程。

就您而言,将 DataType 设为枚举并没有太大优势。你可以在 switch-case 中使用枚举,仅此而已,很重要。 DataType 的非枚举版本工作得很好。

I don't think it is impossible to have generified enum. If you could hack into compiler, you can have a subclass of Enum that is generic, and the class file of your generic enum wouldn't cause problems.

But in the end, enum is pretty much a syntax sugar. In C, C++, C#, enums are basically alias for int constants. Java gives it more power, but it is still supposed to represent simple items.

Somewhere people have to draw the line. Just because a class has enumerated instances, doesn't mean it must be an enum. If it is sophisticated enough in other areas, it deserves to be a regular class.

In your case, there is not much advantage to make DataType an enum. You can use enum in switch-case, that's about it, big deal. The non-enum verion of DataType works just fine.

一萌ing 2024-10-26 15:00:42

这就是我的想法——

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

对我来说,枚举器不是您创建实例的类型。它和单例是一样的。所以我可以明白为什么 JAVA 不允许 Enum 泛型,因为你确实无法创建 Enum 类型的新实例来像使用类一样使用临时实例。枚举应该是静态的,并且全局只有一个实例。对我来说,允许全局只有一个实例的类使用泛型是没有意义的。

我希望这有帮助。

This is how I think of it -

Regular classes have instances. You create a new instance of a class use it for some purpose and then dispose it. For example List<String> is a list of strings. I can do what ever I want to do with strings and then when I am done I can later do the same functionality with integers.

To me enumerators are not types that you create instances of. Its same thing as a singleton. So I can see why JAVA wouldn't allow generics for Enums because you really can't create a new instance of type Enum to use temporary like you do with classes. Enums are supposed to be static and only have one instance globally. To me, it wouldn't make sense to allow generics for a class that only has one instance globally.

I hope this helps.

从此见与不见 2024-10-26 15:00:42

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

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

  • for Datatype.INT: int parse(String)
  • for Datatype.VARCHAR: String parse(String)
  • 等等

那么编译器如何能够进行类似以下内容的类型检查:

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

???

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

I think that the reason why you wish to parameterize the enum with <T> boils down to being able to have different method signatures for the various constants of the enum.

In your example, the signature (type of parameters and return type) for parse would be:

  • for Datatype.INT: int parse(String)
  • for Datatype.VARCHAR: String parse(String)
  • and so on

So how would the compiler be able to typecheck something like:

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

???

To apply static typing and typechecking to this kind of expression, the signature of the method must be the same for all the instances. However, in the end you suggest to have different method signatures for different instances... That's why it's not possible to do it in Java.

岁月如刀 2024-10-26 15:00:42
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 行的断言均为 true。泛型类型在运行时不可用。

枚举的优点之一是您可以使用 == 来比较它们。第 16 行的断言将评估为 true。

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

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

我希望这有帮助。

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

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
}

The asserts at line 16 and 17 are both true. The generic types are not available at run time.

One of the advantages of an enum is that you can use == to compare them. The assert at line 16 will evaluate to true.

At line 19 we run into a problem though. longGE and intGE are the same object (as the assert at line 16 shows.) What will be returned by the parse("1")? The generic type information is not available at run time. So there would be no way to determine T for the parse method at run time.

Enums are basically static, they only exist once. And it doesn't make sense to apply generic typing to static types.

I hope this helps.

Note - this is not meant to be working code. It is using the syntax suggested in the original question.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文