Home > Software design >  Proving Enum classes are Enum classes when they're passed or stored
Proving Enum classes are Enum classes when they're passed or stored

Time:06-14

If I pass to a method, or store in a variable, a collection of classes that I know are all Enums (ideally guaranteed, but I don't mind casting), is there a way to let the compiler know they are enum classes, in order to use methods like values(), EnumSet.allOf(), etc? I.e., I'm looking at cases where I cannot refer to the names of the Enum classes directly.

I believe this is not possible, but wanted to verify.

An example might look something like this:

enumClasses.stream()
        .map(eclass -> EnumSet.allOf(eclass))
        ... more here...

but I don't see a way to prove in the declaration of enumClasses (as variable or parameter) that it's only enums.

Examples: Some cases I tried that did not work using Class<? extends Enum<?>>>

    List<? extends Class<? extends Enum<?>>> enums = List.of(MyEnum.class); 
    enums.forEach(eclass -> EnumSet.allOf(eclass)); // error here.

or

    Class<? extends Enum<?>> enumClass = MyEnum.class;
    EnumSet.allOf(enumClass); // error here.
    enumClass.values(); // error here.

I also tried creating this helper-method signature:

static <E extends Enum<E>> EnumSet myValues(Class<E> iEnumClass) {
    return EnumSet.allOf(iEnumClass);
}

and the method compiles fine, but I have the same problems as above when I try to call the method (unless I call that method directly with the class-name, like myValues(MyEnum.class))

CodePudding user response:

I discovered in a linked question that there's a method on Class called getEnumConstants()

(It simply returns null if it's not an enum, but in my case I know that it is. isEnum() is also provided if needed.)

List<Class<?>> enumClasses; // or List<Class<? extends MyInterface>>
enumClasses.stream()
    .map(Class::getEnumConstants)
    .flatMap(Arrays::stream)
    ...

This doesn't directly solve the question as I posed it in the title, but it does solve the use-case I had, which was to collect the values of several same-interface enums.

CodePudding user response:

The problem you are having is with the bound <E extends Enum<E>>, which exists on both EnumSet.allOf() (as well as the EnumSet class itself) and your myValues() method. Basically, you need a "list of classes, each of which inherits Enum of itself", and there is no way to express that in Java.

With Class<? extends Enum<?>>, you are giving up the requirement that the class inherit Enum of itself, so it's possible to put some invalid values in there. For example, someone could put Enum.class itself (the class object representing the Enum class itself) in there. Or, someone could take an enum with an enum constant that implements custom methods; such an enum constant would be an instance of an anonymous subclass of the enum. So running .getClass() on this would give you a Class which is a subclass of Enum, but it is not the actual class of the enum that you want.

If you want to hold a list of enum classes safely, you would probably have to define your own custom class ListOfEnumClasses or something like that. The add() method would be generic and enforce the relationship on the type variable, like

public <E extends Enum<E>> void add(Class<E> clazz) { }

Internally you might still hold it as a List<Class<? extends Enum<?>>, but you would not expose this variable to the outside. To pass one of the elements to something like EnumSet.allOf() internally, you would probably have to cast it to a raw type to turn off generics, like: EnumSet.allOf((Class) eclass)

  • Related