Why in Java Enum Is Declared as Enum<E Extends Enum<E>>

Confused about the generic which extends an exist Enum type

Enum<E extends Enum<E>> is very difficult to understand.

Let's consider two enums.

enum Colour { RED, GREEN, BLUE }

enum Shape { SQUARE, CIRCLE, TRIANGLE }

Think about the compareTo() method in Enum. This is used to compare different constants in the same Enum based on the order they are declared in. If Enum were not a generic class, the signature for this method would have to be int compareTo(Enum e). But that wouldn't make any sense because then you could compare a Colour to a Shape. We want the signature of compareTo in Colour to be int compareTo(Colour colour). The only way to do that is if Enum is a generic class with a type paramter E and the type parameter for Colour is Colour! As long as the type parameter of Colour is Colour and the type parameter of Shape is Shape then we can only compare Colours to Colours and Shapes to Shapes.

So in the definition of Enum we want some way of expressing that, for every subclass, the type parameter must not only be an enum but the type parameter should be the subclass itself. Therefore E should not just extend Enum but E should extend Enum<E>!

This is where Enum<E extends Enum<E>> comes from.

(You should not write Enum<E extends Enum<?>>).

One way to assign a value to enumType is to pass a Class<E> to the constructor. Like this

class MyClass2<E extends Enum<E>> {
private final Class<E> enumType;

MyClass2(Class<E> enumType) {
this.enumType = enumType;
}
}

If you don't want to do that, another way to get the Class<E> object at runtime is to use the Enum instance method getDeclaringClass(). This requires you to have an instance of E at hand to call the method on though. One way of getting an array of all enum constants at runtime is to write e.getDeclaringClass().getEnumConstants().

You cannot write E.class because, as you rightly say, generics are implemented using the process of type erasure. At runtime, an ArrayList<String> is just an ArrayList, so it is impossible to access the type parameter.

Java Enum definition

It means that the type argument for enum has to derive from an enum which itself has the same type argument. How can this happen? By making the type argument the new type itself. So if I've got an enum called StatusCode, it would be equivalent to:

public class StatusCode extends Enum<StatusCode>

Now if you check the constraints, we've got Enum<StatusCode> - so E=StatusCode. Let's check: does E extend Enum<StatusCode>? Yes! We're okay.

You may well be asking yourself what the point of this is :) Well, it means that the API for Enum can refer to itself - for instance, being able to say that Enum<E> implements Comparable<E>. The base class is able to do the comparisons (in the case of enums) but it can make sure that it only compares the right kind of enums with each other. (EDIT: Well, nearly - see the edit at the bottom.)

I've used something similar in my C# port of ProtocolBuffers. There are "messages" (immutable) and "builders" (mutable, used to build a message) - and they come as pairs of types. The interfaces involved are:

public interface IBuilder<TMessage, TBuilder>
where TMessage : IMessage<TMessage, TBuilder>
where TBuilder : IBuilder<TMessage, TBuilder>

public interface IMessage<TMessage, TBuilder>
where TMessage : IMessage<TMessage, TBuilder>
where TBuilder : IBuilder<TMessage, TBuilder>

This means that from a message you can get an appropriate builder (e.g. to take a copy of a message and change some bits) and from a builder you can get an appropriate message when you've finished building it. It's a good job users of the API don't need to actually care about this though - it's horrendously complicated, and took several iterations to get to where it is.

EDIT: Note that this doesn't stop you from creating odd types which use a type argument which itself is okay, but which isn't the same type. The purpose is to give benefits in the right case rather than protect you from the wrong case.

So if Enum weren't handled "specially" in Java anyway, you could (as noted in comments) create the following types:

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

Second would implement Comparable<First> rather than Comparable<Second>... but First itself would be fine.

Generics and Class? extends Enum?, EnumSet.allOf(class) vs class.getEnumConstants()

My guess is that in ? extends Enum<?> the two ? could be different whereas allOf expects a T extends Enum<T> where both T are the same.

For example, consider the following code:

static enum MyEnum {}
static class EnumValue<T extends Enum<T>> {
Class<T> enumClass;
EnumValue(Class<T> enumClass) {
this.enumClass = enumClass;
}
Class<T> enumClass() { return enumClass; }
}

These lines will compile:

EnumValue<?> enumValue = new EnumValue(MyEnum.class); // raw constructor
Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumValue.enumClass());

because we know that the two T in enumValue.enumClass() are the same but this won't:

EnumValue enumValue = new EnumValue(MyEnum.class);
Class<? extends Enum<?>> enumSelected = enumValue.enumClass();
Set<? extends Enum<?>> enumInstances = EnumSet.allOf(enumSelected);

because you have lost information by using a Class<? extends Enum<?>> as an intermediate step.

Why does T extends EnumT & SomeInterface compile, but not T extends SomeInterface & EnumT?

If you look at the syntax for type parameter bounds in JLS 8.1.2 you'll see:

TypeBound:
extends TypeVariable
extends ClassOrInterfaceType {AdditionalBound}

AdditionalBound:
& InterfaceType

In other words, only the first type specified can be a class - all the rest have to be interfaces.

Aside from anything else, this prevents multiple classes being specified.

It also mirrors the way that when declaring a class, you have to put the class it's extending first, then the interfaces it implements - not the other way round.

Is it possible to extend enum in Java 8?

You are over-complicating your design. If you are willing to accept that you can invoke a default method on an instance only, there entire code may look like this:

interface ReverseLookupSupport<E extends Enum<E>> {
Class<E> getDeclaringClass();
default E lookup(String name) {
try {
return Enum.valueOf(getDeclaringClass(), name);
} catch(IllegalArgumentException ex) { return null; }
}
}
enum Test implements ReverseLookupSupport<Test> {
FOO, BAR
}

You can test it with:

Test foo=Test.FOO;
Test bar=foo.lookup("BAR"), baz=foo.lookup("BAZ");
System.out.println(bar+" "+baz);

An non-throwing/catching alternative would be:

interface ReverseLookupSupport<E extends Enum<E>> {
Class<E> getDeclaringClass();
default Optional<E> lookup(String name) {
return Stream.of(getDeclaringClass().getEnumConstants())
.filter(e->e.name().equals(name)).findFirst();
}

to use like:

Test foo=Test.FOO;
Test bar=foo.lookup("BAR").orElse(null), baz=foo.lookup("BAZ").orElse(null);
System.out.println(bar+" "+baz);

How to extends Enum in Java?

You cannot extend from Enum. You have to declare an Enum class like:

public enum Q {
TYPE1, TYPE2, TYPE3;
}

And you also cannot instantiate an enum class directly. Each type of your enum class is instantiated exactly once by the virtual machine.

public class MyClass {

public enum MyEnum{
TYPE1("Name", 9,1,100000), TYPE2("Name2", 10, 1, 200000);

private final int androidVersion;
private final int appVersionCode;
private final int availableMemSize;
private final String appVersionName;

private MyEnum(String appVersionName, int androidVersion, int appVersionCode, int availableMemSize) {
this.androidVersion = androidVersion;
this.appVersionCode = appVersionCode;
this.availableMemSize = availableMemSize;
this.appVersionName = appVersionName;
}
}
MyEnum mType = MyEnum.TYPE1;
}

Can enums be subclassed to add new elements?

No, you can't do this in Java. Aside from anything else, d would then presumably be an instance of A (given the normal idea of "extends"), but users who only knew about A wouldn't know about it - which defeats the point of an enum being a well-known set of values.

If you could tell us more about how you want to use this, we could potentially suggest alternative solutions.



Related Topics



Leave a reply



Submit