58

I'm writing a library, which has a predefined set of values for an enum. Let say, my enum looks as below.

public enum EnumClass {
    FIRST("first"),
    SECOND("second"),
    THIRD("third");

    private String httpMethodType;

}

Now the client, who is using this library may need to add few more values. Let say, the client needs to add CUSTOM_FIRST and CUSTOM_SECOND. This is not overwriting any existing values, but makes the enum having 5 values.

After this, I should be able to use something like <? extends EnumClass> to have 5 constant possibilities.

What would be the best approach to achieve this?

Kiran A B
  • 901
  • 1
  • 7
  • 13
  • 1
    this will be helpful http://stackoverflow.com/a/478431/3138755 – Rahul Feb 26 '16 at 11:05
  • 2
    Enums are good when you're definitely certain on the number of known-ahead possible values. If you expect it to be extended, you probably should never use enums, and review the subject your current enum is designed for and probably drop it in favor of classes implementing a well designed interface. – Lyubomyr Shaydariv Feb 26 '16 at 11:26

5 Answers5

70

You cannot have an enum extend another enum, and you cannot "add" values to an existing enum through inheritance.

However, enums can implement interfaces.

What I would do is have the original enum implement a marker interface (i.e. no method declarations), then your client could create their own enum implementing the same interface.

Then your enum values would be referred to by their common interface.

In order to strenghten the requirements, you could have your interface declare relevant methods, e.g. in your case, something in the lines of public String getHTTPMethodType();.

That would force implementing enums to provide an implementation for that method.

This setting coupled with adequate API documentation should help adding functionality in a relatively controlled way.

Self-contained example (don't mind the lazy names here)

package test;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<HTTPMethodConvertible> blah = new ArrayList<>();
        blah.add(LibraryEnum.FIRST);
        blah.add(ClientEnum.BLABLABLA);
        for (HTTPMethodConvertible element: blah) {
            System.out.println(element.getHTTPMethodType());
        }
    }

    static interface HTTPMethodConvertible {
        public String getHTTPMethodType();
    }
    static enum LibraryEnum implements HTTPMethodConvertible {
        FIRST("first"),
        SECOND("second"),
        THIRD("third");
        String httpMethodType;
        LibraryEnum(String s) {
            httpMethodType = s;
        }
        public String getHTTPMethodType() {
            return httpMethodType;
        }
    }
    static enum ClientEnum implements HTTPMethodConvertible {
        FOO("GET"),BAR("PUT"),BLAH("OPTIONS"),MEH("DELETE"),BLABLABLA("POST");
        String httpMethodType;
        ClientEnum(String s){
            httpMethodType = s;
        }
        public String getHTTPMethodType() {
            return httpMethodType;
        }
    }
}

Output

first
POST
Mena
  • 47,782
  • 11
  • 87
  • 106
  • I liked this approach. However, one query. Usually while we use enum in code to use it as default value (a constant from a given list), we use something like `LibraryEnum.FIRST` . How would you suggest to use when it is via interface? – Kiran A B Feb 26 '16 at 11:57
  • @KiranAB you can declare/implement some `getDefaultValue` method, returning the `interface`'s type. This way each `enum` will have to implement it, and return a default value (or `null`). – Mena Feb 26 '16 at 12:04
  • One more question. I have added a method, which works similar to valueOf. Now how do I give it static access, because interface cannot have static methods. And if I don't add it in interface, how do I enforce client to implement such a method.. – Kiran A B Feb 29 '16 at 05:57
  • Basically I need to have some method where given a String value, I can get interface's type of instance. – Kiran A B Feb 29 '16 at 05:58
  • If i have to use valueOf how i can have common interface which does that? – Kulbhushan Singh Jun 02 '20 at 05:02
13

Enums are not extensible. To solve your problem simply

  • turn the enum in a class
  • create constants for the predefined types
  • if you want a replacement for Enum.valueOf: track all instances of the class in a static map

For example:

public class MyType {
    private static final HashMap<String,MyType> map = new HashMap<>();
    private String name;
    private String httpMethodType;

    // replacement for Enum.valueOf
    public static MyType valueOf(String name) {
         return map.get(name);
    }

    public MyType(String  name, String httpMethodType) {
         this.name = name;
         this.httpMethodType = httpMethodType;
         map.put(name, this);
    }

    // accessors
    public String name() { return name; }
    public String httpMethodType() { return httpMethodType; }

    // predefined constants
    public static final MyType FIRST = new MyType("FIRST", "first");
    public static final MyType SECOND = new MyType("SECOND", "second");
    ...
}
wero
  • 32,544
  • 3
  • 59
  • 84
  • This is what I had in mind. I'm quite new to java(started using it around 40 days ago), so couldn't say if this is the right construct. It's just that it looks slightly messy. Would you advise to clean it up a lil bit? – Kiran A B Feb 26 '16 at 12:02
  • are you sure that new `MyType("FIRST", "first");` will be called before `map = new HashMap<>();`? in other words, are you sure that `map` will not be null when `map.add()` is called? unfortunately the occurring error will be `NoClassDefFound` and it doesn't help to find the problem. check my answer that works. https://paste.ubuntu.com/p/CNQms52Ggz/ or https://stackoverflow.com/a/69961845/8430173 – Negar Zamiri Nov 14 '21 at 09:37
11

Think about Enum like a final class with static final instances of itself. Of course you cannot extend final class, but you can use non-final class with static final instances in your library. You can see example of this kind of definition in JDK. Class java.util.logging.Level can be extended with class containing additional set of logging levels.

If you accept this way of implementation, your library code example can be like:

public class EnumClass {
    public static final EnumClass FIRST = new EnumClass("first");
    public static final EnumClass SECOND = new EnumClass("second");
    public static final EnumClass THIRD = new EnumClass("third");

    private String httpMethodType;

    protected EnumClass(String name){
        this.httpMethodType = name;
    }
}

Client application can extend list of static members with inheritance:

public final class ClientEnum extends EnumClass{
    public static final ClientEnum CUSTOM_FIRST = new ClientEnum("custom_first");
    public static final ClientEnum CUSTOM_SECOND = new ClientEnum("custom_second");

    private ClientEnum(String name){
        super(name);    
    }
}

I think that this solution is close to what you have asked, because all static instances are visible from client class, and all of them will satisfy your generic wildcard.

maddingl
  • 197
  • 4
  • 20
Fertonder Brate
  • 148
  • 1
  • 7
3

We Fixed enum inheritance issue this way, hope it helps

Our App has few classes and each has few child views(nested views), in order to be able to navigate between childViews and save the currentChildview we saved them as enum inside each Class. but we had to copy paste, some common functionality like next, previous and etc inside each enum. To avoid that we needed a BaseEnum, we used interface as our base enum:

public interface IBaseEnum {
    IBaseEnum[] getList();
    int getIndex();

    class Utils{
        public IBaseEnum next(IBaseEnum enumItem, boolean isCycling){
            int index = enumItem.getIndex();
            IBaseEnum[] list = enumItem.getList();
            if (index + 1 < list.length) {
                return list[index + 1];
            } else if(isCycling)
                return list[0];
            else
                return null;
        }

        public IBaseEnum previous(IBaseEnum enumItem, boolean isCycling) {
            int index = enumItem.getIndex();
            IBaseEnum[] list = enumItem.getList();

            IBaseEnum previous;
            if (index - 1 >= 0) {
                previous = list[index - 1];
            }
            else {
                if (isCycling)
                    previous = list[list.length - 1];
                else
                    previous = null;
            }
            return previous;
        }
    }
}

and this is how we used it

enum ColorEnum implements IBaseEnum {
    RED,
    YELLOW,
    BLUE;
    @Override
    public IBaseEnum[] getList() {
        return values();
    }

    @Override
    public int getIndex() {
        return ordinal();
    }

    public ColorEnum getNext(){
    return (ColorEnum) new Utils().next(this,false);
    }

    public ColorEnum getPrevious(){
        return (ColorEnum) new Utils().previous(this,false);
    }
}

you could add getNext /getPrevious to the interface too

Annie
  • 51
  • 7
1

@wero's answer is very good but has some problems:
the new MyType("FIRST", "first"); will be called before map = new HashMap<>();. in other words, the map will be null when map.add() is called. unfortunately, the occurring error will be NoClassDefFound and it doesn't help to find the problem. check this:

public class Subject {

    // predefined constants
    public static final Subject FIRST;
    public static final Subject SECOND;
    private static final HashMap<String, Subject> map;

    static {
        map = new HashMap<>();

        FIRST = new Subject("FIRST");
        SECOND = new Subject("SECOND");
    }

    private final String name;

    public Subject(String name) {
        this.name = name;
        map.put(name, this);
    }

    // replacement for Enum.valueOf
    public static Subject valueOf(String name) {
        return map.get(name);
    }

    // accessors
    public String name() {
        return name;
    }
Negar Zamiri
  • 641
  • 2
  • 11
  • 16