Can Enums Be Subclassed to Add New Elements

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.

Adding items to external enum [java]

I need to add a new value to the enum

Its impossible. But you can edit source code of library. Or change your design keep enum as is.

Can I add and remove elements of enumeration at runtime in Java

No, enums are supposed to be a complete static enumeration.

At compile time, you might want to generate your enum .java file from another source file of some sort. You could even create a .class file like this.

In some cases you might want a set of standard values but allow extension. The usual way to do this is have an interface for the interface and an enum that implements that interface for the standard values. Of course, you lose the ability to switch when you only have a reference to the interface.

Extending a enum in Java

You can't.

Enum types are final by design.

The reason is that each enum type should have only the elements declared in the enum (as we can use them in a switch statement, for example), and this is not possible if you allow extending the type.

You might do something like this:

public interface MyInterface {
// add all methods needed here
}

public enum A implements MyInterface {
A, B;
// implement the methods of MyInterface
}

public enum B implements MyInterface {
C;
// implement the methods of MyInterface
}

Note that it is not possible to do a switch with this interface, then. (Or in general have a switch with an object which could come from more than one enum).

How to extend Python Enum?

Subclassing an enumeration is allowed only if the enumeration does not define any members.

Allowing subclassing of enums that define members would lead to a violation of some important invariants of types and instances.

https://docs.python.org/3/library/enum.html#restricted-enum-subclassing

So no, it's not directly possible.

Can a Java interface be defined such that only Enums can extend it?

Java by default does not support anything like that, you ask why not with link to specification, but there is no special reason why, just no one decided to add such feature, you could propose it yourself - but then you will probably learn that they don't think it's something needed and will not add this to the language.

But java provides pretty powerful option to implement this by yourself: annotation processing.

I've created simple java 8 maven project with annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface EnumInterface {}

And with special processor

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.*;

@SupportedAnnotationTypes("com.gotofinal.enuminterface.EnumInterface")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EnumInterfaceProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
Types typeUtils = processingEnv.getTypeUtils();

// first we scan for all interfaces marked with this annotation
List<TypeElement> enumOnlyInterfaces = new ArrayList<>();
for (Element rootElement : roundEnv.getRootElements()) { // getRootElements should return all types being compiled
if (! (rootElement instanceof TypeElement)) {
continue;
}
TypeMirror typeMirror = rootElement.asType();
// we check if this class have our annotation, we could also here check if this is an interface (by checking if it does not extend Object directly) and throw error otherwise
if (rootElement.getAnnotation(EnumInterface.class) != null) {
enumOnlyInterfaces.add((TypeElement) rootElement);
}
}

// and now we scan for any non enum types that implement this interface
for (Element rootElement : roundEnv.getRootElements()) {
if (! (rootElement instanceof TypeElement)) {
continue;
}
TypeElement type = findImplementedInterface(rootElement.asType(), enumOnlyInterfaces, typeUtils);
if (type == null) {
continue;
}
if (! (rootElement.asType() instanceof DeclaredType)) {
continue;
}

// it's fine if it is an enum
if (this.isEnum(rootElement.asType(), typeUtils)) {
continue;
}

// and we print error to compiler
messager.printMessage(Diagnostic.Kind.ERROR, "Interface " + type.getQualifiedName()
+ " can't be used on non enum class: " + ((TypeElement) rootElement).getQualifiedName());
}
return false;
}

public TypeElement findImplementedInterface(TypeMirror type, List<TypeElement> interfaces, Types types) {
for (TypeElement anInterface : interfaces) {
// types.isSubtype(typeA, typeA) would return true, so we need to add this equals check
if (!anInterface.asType().equals(type) && types.isSubtype(type, anInterface.asType())) {
return anInterface;
}
}
return null;
}

// maybe there is better way to do this... but I just scan recursively for a subtype with java.lang.Enum name, so it's not perfect but should be enough.
public boolean isEnum(TypeMirror type, Types types) {
for (TypeMirror directSupertype : types.directSupertypes(type)) {
TypeElement element = (TypeElement) ((DeclaredType) directSupertype).asElement();
if (element.getQualifiedName().contentEquals("java.lang.Enum")) {
return true;
}
if (isEnum(directSupertype, types)) {
return true;
}
}
return false;
}
}

And register it in META-INF/services/javax.annotation.processing.Processor file:

com.gotofinal.enuminterface.EnumInterfaceProcessor

This code could be probably improved a lot, I've never wrote any annotation processor before. But when we will create another maven project and declare this one as dependency and write code like this:

@EnumInterface
interface TestInterface {}

enum TestEnum implements TestInterface {}

class TestClass implements TestInterface {}

We will not be able to compile it with error:

Interface com.gotofinal.enuminterface.TestInterface can't be used on non enum class: com.gotofinal.enuminterface.TestClass

Java: an enum in an extending class

Enums in Java cannot be extended with new elements. If you want two distinct classes to use two very similar Enums, the right approach is to make them both use the same single Enum class (Color).

Your best option for restricting the subset of Colors allowed for each type of Dog is runtime validation using an inherited superclass method - named, say, getValidColors(); and marking color as final so the validation cannot be (easily) bypassed.

public enum Color {
BLACK, GREEN, INVISIBLE, RED, SABLE
}

public abstract class Dog {
protected final Color color;
public Dog(final Color color) {
if (!this.getValidColors().contains(color)) {
throw new IllegalArgumentException(color);
}
this.color = color;
}
public abstract List<Color> getValidColors();
}

public class Shepherd extends Dog {
public Shepherd(final Color color) {
super(color);
}
@Override
public List<Color> getValidColors() {
return Arrays.asList(Color.BLACK, Color.GREEN, Color.INVISIBLE);
}
}

public class Corgi extends Dog {
public Corgi(final Color color) {
super(color);
}
@Override
public List<Color> getValidColors() {
return Arrays.asList(Color.RED, Color.BLACK, Color.SABLE);
}
}


Related Topics



Leave a reply



Submit