开发者

Java enums and generics

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


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).


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.


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜