1

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

Joshua Goldberg
  • 5,059
  • 2
  • 34
  • 39
  • 1
    `List extends Class extends Enum>>>` might work. – Louis Wasserman May 31 '22 at 22:28
  • 1
    You can look at the definition of `EnumSet` to see how to do this with generics. I'm not sure what your actual requirements are though. `Class EnumSet>` – markspace Jun 01 '22 at 00:15
  • @LouisWasserman, I don't think so, or at least I can't get it to work, see counterexamples I've added. – Joshua Goldberg Jun 01 '22 at 00:44
  • @markspace, I want to collect the values of several enums passed to a method. (The enum objects themselves implement an additional interface for further processing downstream, but for now I just wish to collect values, given a Class object where I know that the class is an Enum.) – Joshua Goldberg Jun 01 '22 at 00:44
  • This [related answer](https://stackoverflow.com/a/8704051/411282) gives a potential workaround, though it requires a wrapper class **and** the wrapper class must be specialized to any additional interfaces I wish for the enum to implement. (I.e., if I want to know that the values I'm getting are of type `MyEnumInterface` I'd need one wrapper class for that case, rather than a wrapper that can be used generically for other cases like this.) – Joshua Goldberg Jun 01 '22 at 01:18
  • (I don't need the `valueOfSearchName` or the index, but the wrapper supports adding a `values()` method that returns a `List`. or `List`) – Joshua Goldberg Jun 01 '22 at 01:21
  • Sorry for the noise, but even with the wrapper it's hard, because I can't call the constructor or static factory using an object typed with my Interface, **even** if the wrapper class is specialized to that interface with `&`. – Joshua Goldberg Jun 01 '22 at 01:38
  • "Something like the following doesn't work": doesn't work how? – user207421 Jun 01 '22 at 01:53
  • @user207421, if I define the variable that way, I cannot use methods that require enums, like `values()` or `EnumSet.allof()`. I expanded the examples below. – Joshua Goldberg Jun 01 '22 at 01:55
  • You've been here long enough to know that this is not sufficient. "Doesn't work" and "I cannot" are not problem descriptions. – user207421 Jun 01 '22 at 03:31
  • Thanks, @user207421, I've cleaned up the text a bit to clarify that the cases below were the examples showing that that type declaration was not sufficient. – Joshua Goldberg Jun 01 '22 at 14:14
  • Have you tried using an interface? If you give all of your enums a common interface, you simplify your problem greatly. – davidalayachew Jun 05 '22 at 09:37
  • 1
    @davidalayachew, they do have a common interface, but the problem is that when I have an object of type `MyInterface` I can't call methods like `values()` `EnumSet.allOf()` on that object even though I know it will be an enum. You can't, for instance, say `interface MyInterface extends Enum` – Joshua Goldberg Jun 06 '22 at 13:35

2 Answers2

0

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.

Joshua Goldberg
  • 5,059
  • 2
  • 34
  • 39
  • @Holger There are a variety of different enum Classes being passed in. MyEnum1, MyEnum2, ... if you like. If it were just the one, I'd spell out MyEnum.class in the code where I need the values. – Joshua Goldberg Jun 01 '22 at 14:08
  • 1
    But then, you can’t declare your list as `List>` – Holger Jun 01 '22 at 14:48
  • @Holger, now I see your point, and it was a mistake (edited now). At that spot in the code sample, I meant to refer to an interface that the different enums implement, not a particular concrete enum class. If the enums don't implement a shared interface (or even if they do) it would be fine to change to `List>` or it could be `List>` – Joshua Goldberg Jun 01 '22 at 21:49
0

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)

newacct
  • 119,665
  • 29
  • 163
  • 224
  • The key for me here is the last line:`EnumSet.allOf` won't complain if you cast the argument to raw `Class`. A holder-class could help guarantee its arguments fit the invariant, but if it returned an object to pass to another method it would have to "regress" to `Class extends Enum>>` (still requiring the raw cast or the reflection in my own answer) since the various objects have different `E`. – Joshua Goldberg Jun 14 '22 at 20:46