Why Shouldn't Java Enum Literals Be Able to Have Generic Type Parameters

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

This has been discussed as of JEP-301 Enhanced Enums, which was withdrawn, regrettably. The example given in the JEP is, which is precisely what I was looking for:

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

Unfortunately, the JEP was struggling with significant issues, which couldn't be resolved: http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html

Java enums and generics

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

Trouble declaring generic class with type of member of enum value

I think you want to be able to change the enum and have everything else remain compilable.

That's pretty impossible as types are not really first-class data in Java. Your client code needs to use a strongly typed object and you can't get that back from a heterogeneous enum or any other heterogeneous data structure at runtime.

You could include these getters like functional objects in the enum and use delegation to embed business logic, but you're still going to end up casting.

public class ColValsGetter<T> {
protected List<T> objs;
protected Column<T> col;
public ColValsGetter(List<T> objs, Column<T> col) {
this.objs = objs;
this.col = col;
}
public T getColValForObj(int index) {
return col.getColVal(objs.get(index));
}
}
public class ColValsGetterFactory< T > {
public ColValsGetterFactory() {}
public ColValsGetter< T > make( List< T > lt, Column< T > col ) {
return new ColValsGetter< T >( lt, col );
}
}
public enum ColTypes {
VARCHAR( String.class, new ColValsGetterFactory< String >() ),
INTEGER( Integer.class, new ColValsGetterFactory< Integer >() );

public final Class<?> dataType;
public final ColValsGetterFactory< ? > gf;
public < T > ColTypes ( Class< T > dataType, ColValsGetterFactory< T > gf ) {
this.dataType = dataType;
this.gf = gf;
}
}

public class DBRowFooColValsGetter {
private String fooVal;
private ColValsGetter< String > getter;
public DBRowFooColValsGetter( List< DBRow > rows ) {
this.getter = ( ColValsGetter< String > )DBRow.DBCols.FOO.colType.gf.make( rows, DBRow.DBCols.FOO );
}
public void complexLogicForFooOnly( int i ) {
fooVal = getter.getColValForObj( i );
// do a lot of stuff that only makes sense for the FOO column
};
}

Calling Enum.values() on a generic type

A common way to do this is to pass the target class object (or an object of the target class) as an argument to the function, then use methods from the class object to do the tricky part. For enum classes, the class object has a method which returns the equivalent of Enum.values(). You can use that to get the enum values from the target class:

public class Scratch {
enum lower { a, b, c };
enum upper { A, B, C };

static <T extends Enum<T>> T translate(Enum<?> aFrom, Class<T> aTo) {
return aTo.getEnumConstants()[aFrom.ordinal()];
}

public static void main(String[] args) {
for (lower ll : lower.values()) {
upper uu = translate(ll, upper.class);
System.out.printf("Upper of '%s' is '%s'\n", ll, uu);
}
for (upper uu : upper.values()) {
lower ll = translate(uu, lower.class);
System.out.printf("Lower of '%s' is '%s'\n", uu, ll);
}
}
}

Running this produces the following output:

Upper of 'a' is 'A'
Upper of 'b' is 'B'
Upper of 'c' is 'C'
Lower of 'A' is 'a'
Lower of 'B' is 'b'
Lower of 'C' is 'c'

Troubles with generics while trying to call a method

The field validator in your enum is of type DataValidator<?> which will not match DataValidator<T>.

However, you could work around that by creating an accessor method for the field that has the correct signature:

      public <T> DataValidator<T> getValidator() {
return (DataValidator<T>)validator;
}

You will need to cast here because you are assuming that the type passed in T actually belongs to the enum constant used.

In other words, the system won't be able to prevent you from doing silly things like:

TAX_CODE.getValidator().validate(new HashMap<>());

There will be no check that TAX_CODE has to be a String, it will fail at run-time with a ClassCastException.

Here's the adjusted code:

  public enum ValidationType {
TAX_CODE(null); // FIXME

private final DataValidator<?> validator;

private ValidationType(DataValidator<?> validator) {
this.validator = validator;
}

public <T> DataValidator<T> getValidator() {
return (DataValidator<T>)validator;
}
}

public static <T> void validate(T data, ValidationType dataType) {
dataType.getValidator().validate(data);
}

private static abstract class DataValidator<T> {
public abstract Class<T> getType();
public abstract void validate(T data);
}

Update

A rewritten proposal:

If you declare the DataValidators as constants like this, then they would be type safe:

public static class Validators {
public static final DataValidator<String> TAX_CODE_VALIDATOR = ...;
}

Usage:

Validators.TAX_CODE_VALIDATOR.validate("12345");

Generic class field in enum

As explained by Jorn Vernee, enum types do not allow this.

The most concise solution allowing you to write something like

Class<FirstDictionary> clazz = Dictionaries.FIRST_DICTIONARY.clazz();

would be

public interface Dictionaries<T extends DictionaryModel> {
Dictionaries<FirstDictionary> FIRST_DICTIONARY = () -> FirstDictionary.class;
Dictionaries<SecondDictionary> SECOND_DICTIONARY = () -> SecondDictionary.class;

Class<T> clazz();
}

But you may reconcider whether getByValue really needs to receive an enum constant as argument instead of accepting a Class<T extends DictionaryModel> in the first place.

Generic method to convert one enum type to another

I think TacheDeChoco is on the right track, but he misses the necessary generics declarations. Also, since generics are implemented through type erasure, you need to pass in the class of the second enum type.

Assuming the mapping strategy is as simple as "taking the corresponding enum values being at the exact same position", the following could to the job:

public record EnumCaster() {
public static <E1 extends Enum<E1>, E2 extends Enum<E2>> E2 CastOneEnumToAnother(E1 input, Class<E2> e2) {
int pos = input.ordinal();
return e2.getEnumConstants()[pos];
}

public static void main(String[] args) {
System.out.println(firstEnum.ALL_PRODUCT);
System.out.println(CastOneEnumToAnother(firstEnum.ALL_PRODUCT, secondEnum.class));
}
}

Constraint generic type for enum type to implement some interface

Declare the following instead:

public <T extends Enum<T> & MyInterface> C1(Class<T> enumClz)

Here, we're declaring T to have multiple upper bounds, which is possible for type parameters.

The declaration <T extends Enum<T extends MyInterface>> is invalid syntax because T must be bounded with a concrete type, but the T extends MyInterface in the type argument for Enum is trying to add more information about T when it's already been declared.

Note also that a class type must always come first when declaring multiple bounds. A declaration of <T extends MyInterface & Enum<T>> is also invalid syntax.

And by the way, out of curious I have an odd question though not really important. Can an enum type T be equivalent to the following infinite loop

<T extends Enum< T extends Enum<T extends<....>>>> ?

The declaration T extends Enum<T> is already "infinite" in that it's recursive. The same T that is being declared is given as a type argument for its upper bound - a type parameter's scope includes its own declaration.

More information:

  • Bounded Type Parameters
  • Can I use a type parameter as part of its own bounds?
  • How do I decrypt "Enum<E extends Enum<E>>"?


Related Topics



Leave a reply



Submit