I have following code:
public interface IEnum {
static <E extends Enum<E> & IEnum> Map<Integer, E> getId2EnumMap(Class<E> clazz) {
Map<Integer, E> temp = new HashMap<>();
for (final E e : clazz.getEnumConstants()) {
temp.put(e.getId(), e);
}
return Collections.unmodifiableMap(temp);
}
int getId();
}
public enum TestEnum2 implements IEnum {
ONE(1),
TWOOOOOOO(22);
public final Integer id;
TestEnum2(int id) {
this.id = id;
}
static final Map<Integer, TestEnum2> enumToId;
static {
enumToId = IEnum.getId2EnumMap(TestEnum2.class);
}
@Override
public int getId() {
return id;
}
}
In this code, all I want to do is not have to pass TestEnum2.class
to IEnum.getId2EnumMap
. Is it possible either by modification of interface signature or some other way?
I have tried multiple threads from Stack Overflow, but every single one of them result in either of two things:
Interface Function cannot be static which results in not being able to call it in TestEnum2 in static block
It can be static but since method is interface method is static, I cannot get concrete type
During compiling all generics are erased. That means that the resulting signature is static Map getId2EnumMap(Class clazz)
. If the signature before erasure would be static <E extends Enum<E> & IEnum> Map<Integer, E> getId2EnumMap()
, during runtime there would be nothing left to identify the type (note that C# which doesn't use erasure and does allow you to read the generic type during runtime). In other words, you need something to be able to get the constants. I see two options:
Class<E> clazz
with E[] constants
and getEnumConstants()
with constants
. The call would become IEnum.getId2EnumMap(values())
.Note that you can make the method a bit easier to read (at least, that's what I think):
static <E extends Enum<E> & IEnum> Map<Integer, E> getId2EnumMap(Class<E> clazz) {
return Arrays.stream(clazz.getEnumConstants())
.collect(Collectors.toUnmodifiableMap(IEnum::getId, Function.identity()));
}
One final remark: you can get rid of the static initializer block by assigning the value directly:
static final Map<Integer, TestEnum2> enumToId = IEnum.getId2EnumMap(TestEnum2.class);
Edit: there is one way to base this on the current class, using StackWalker:
public interface IEnum {
static <E extends Enum<E> & IEnum> Map<Integer, E> getId2EnumMap() {
StackWalker stackWalker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
Class<E> clazz = (Class<E>) stackWalker.getCallerClass();
Map<Integer, E> temp = new HashMap<>();
for (final E e : clazz.getEnumConstants()) {
temp.put(e.getId(), e);
}
return Collections.unmodifiableMap(temp);
}
int getId();
}
public enum TestEnum2 implements IEnum {
ONE(1),
TWOOOOOOO(22);
public final Integer id;
TestEnum2(int id) {
this.id = id;
}
static final Map<Integer, TestEnum2> enumToId = IEnum.getId2EnumMap();
@Override
public int getId() {
return id;
}
}
Note however that the cast is necessary to let clazz.getEnumConstants()
return E[]
but it gives an unchecked cast warning. The compiler won't prevent me from doing this in a completely unrelated class:
static final Map<Integer, TestEnum2> enumToId = IEnum.getId2EnumMap();
This will compile but give a ClassCastException
when you try to get values from the map. You could add some checks in getId2EnumMap
to at least make sure that IEnum.class.isAssignableFrom(clazz)
, but I can still call the above in a different class that implements IEnum
. The issue is that you cannot verify that the generic type E
matches the caller class in any way in Java - because the generic type is simply gone during runtime.