'Java Generics with Enum; return type?
I wrote this function in my Java program:
public <T extends Enum<T>> T getEnum(Class<T> enumClass, String name) {
String value = get(name);
if (value == null) return null;
if (value.length() == 0) return null;
return Enum.valueOf(enumClass, value);
}
This is how I'm calling it:
CollegeDegreeType degreeType = model.getEnum(CollegeDegreeType.class, "degree_type");
It works, but I feel like I should be able to omit the first parameter and infer it from the return type (the type to which the result is being assigned). Is this possible? If so, what's the syntax?
Solution 1:[1]
Let me ask you this: How would you do this without generics? If you can't do it without generics, then you can't do it with generics either. Generics are erased at compile-time and don't exist at runtime (except that generics in class, field, and method declarations can be inspected from the class file at runtime, but that is not relevant here). So if it can be done with generics, you must be able to erase it and do it without generics.
The original method, erased, looks like this:
public Enum getEnum(Class enumClass, String name) {
String value = get(name);
if (value == null) return null;
if (value.length() == 0) return null;
return Enum.valueOf(enumClass, value);
}
But you want to be able to do something like this:
public Enum getEnum(String name) {
String value = get(name);
if (value == null) return null;
if (value.length() == 0) return null;
return ???
}
This isn't possible. Even if Java had no generics, Enum.valueOf() would still need to take enumClass at runtime, because that's how it knows which enum to look for the name in. In fact, there can be multiple enums with a constant with the same name, and EnumA.SOME_CONSTANT would be a different object at runtime from EnumB.SOME_CONSTANT, so how would it know which one you want? It can't.
Perhaps you think that a generic method somehow gets passed its generic parameters (here, T). But that's not what a generic method means. When you write a generic method that is generic on T, that means your method's runtime bytecode must be correct for any T (within the bounds of T), without knowing what T is. The caller can expect that your method will somehow just work no matter what T is (and in fact, the caller might not even know what T is).
Solution 2:[2]
It works, but I feel like I should be able to omit the first parameter and infer it from the return type.
You will not be able to provide T at runtime unless you have either of these:
- method argument of type
T(which you want to omit); - or an instance
Tcomes as a result of the method call; - a hard-coded instance of enum type (which is frankly pointless).
Example (the second and the third option):
public static <T extends Enum<T>> T getEnum(String name) {
T myEnum = (T) getHardCodedEnumMember();
return (T) Enum.valueOf(myEnum.getClass(), name);
}
public static <T extends Enum<T>> T getHardCodedEnumMember() {
return (T) DayOfWeek.SATURDAY;
}
That will work, but I guess that isn't your intention, so I have to stick with passing the Class instance as a parameter.
Another thing to consider is that the method that you've listed might have three outcomes:
- it can return an enum element;
- it might return
nullvalue; - or throw an exception.
Method valueOf() will throw IllegalArgumentException in case if there's no enum member with the matching name. And prior check against null and empty string will not save you from it. At the same time, you need to deal with a nullable result.
You can take another approach by elimination the runtime exceptions and reducing possible outcomes to either an empty optional or optional containing a result. For that you might make use of EnumSet.allOf() to retrieve all contants of the spesified enum type.
public <T extends Enum<T>> Optional<T> getEnum(Class<T> enumClass, String name) {
String value = get(name);
if (value == null || value.length() == 0) {
return Optional.empty();
}
return EnumSet.allOf(enumClass).stream()
.filter(element -> element.name().equals(value))
.findFirst();
}
Solution 3:[3]
Due to generics type erasure the type T is only known at compile time, this prevents you from accessing its class at runtime which you need for Enum.valueOf.
See https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
There is technically a construction possible to retrieve the generic type in some cases, see Get generic type of class at runtime
But i don't think that improves the quality of your code. Your current solution for getting the class is a fine approach in my opinion.
An example implementation that would work, but is hacky* in my opinion (*due to unchecked casts, reflection and anonymous class implementation/extension syntax requirements):
public abstract class MyEnumParser<T extends Enum<T>> {
public T parseEnum(String value) {
return Enum.valueOf(getEnumClass(), value);
}
private Class<T> getEnumClass() {
return (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
}
Usage:
MyEnumParser<CollegeDegreeType> parser = new MyEnumParser<>() {};
System.out.println(parser.parseEnum("degree_type"));
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | newacct |
| Solution 2 | |
| Solution 3 |
