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 DataValidator
s 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
Using Eclipse Java Compiler (Ecj) in Maven Builds
How to Get an Enum Based on the Value of Its Field
Copying Text to the Clipboard Using Java
How to Set Tls Version on Apache Httpclient
How to Get the Http Status Code Out of a Servletresponse in a Servletfilter
How to Compile Java to Native Code
JPA How to Make Composite Foreign Key Part of Composite Primary Key
How to Convert Double[] to Double[]
Difference Between Static and Final
How to Get Java Logging Output to Appear on a Single Line
How to Have Case Insensitive Urls in Spring MVC with Annotated Mappings
Tomcat7 Starts Too Late on Ubuntu 14.04 X64 [Digitalocean]
Download Attachments Using Java Mail