Using Enums to parse generic values in other classes

Viewed 41

I want to create an Enum class that contains a path, and a class type that I can then use to call another class and have the generic type of that already there.

For example:

public enum Settings {
    
    EXAMPLE_1("path here", Integer.class),
    EXAMPLE_2("path here", String.class);
    
    private String path;
    private Class<?> clazz;
    
    Settings(String path, Class<?> clazz){
        this.path = path;
        this.clazz = clazz;
    }
    
    public String getPath() {
        return path;
    }
    
    public Class<?> getClassType(){
        return clazz;
    }
    
}

Then, I would then use this to create another class using the types in this class, like so:

public class Setting <T> {
    
    private T value;
    
    public Setting(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
    
}

I've tried multiple different things, but I'm not sure how to go from the enum, containing the class type, to actually making the Setting class have that type initalised in it.

I then want to have a Map containing the Enum, and then the Setting class. private Map<Settings, Setting> settings = new HashMap<>();

For example, then calling the enum EXAMPLE_2, would return the Setting class with a String, where as EXAMPLE_1 would return it as an Integer.

1 Answers

Enums do not support generics, unfortunately. Generics are compiler-only constructs (well, class files can contain them, but they exist solely for a compiler working off of class files to do its thing), so you must have a situation where the compiler can link it together. Without enums having generics, you can't.

You have 2 broad options:

Make a custom enum value type per value.

Each enum value can be its own little class. Which can override things from the enum as a whole which functions as a superclass of sorts. So, you could do this:

public enum Settings {
    EXAMPLE_1("path here") {
      public Class<Integer> getClassType() { return Integer.class; }
      public Setting<Integer> getSetting() {
        // write however you like it here
      }
    },
    EXAMPLE_2("path here") {
      // similar here
    },

    ;
    
    private String path;
    
    Settings(String path) {
        this.path = path;
    }
    
    public String getPath() {
        return path;
    }
    
    public abstract Class<?> getClassType();
}

But this still won't let you write methods that link things together. The above would let you write: Setting<Integer> setting = Settings.EXAMPLE_1.getSetting(); without any issue, but you can't write something like:

public <T> T showSetting(Settings<T> sEnum) {
  return sEnum.getSetting().getValue();
}

i.e. where a method can be written that is ambivalent about the type of a setting but can 'convey' it across parameter to return type.

Forget enum

enum is just syntax sugar. It's quite a big pile of syntax sugar, but most of that is to ensure that only one instance exists per value, even if you try to serialize them. If the serialization thing isn't relevant, ensuring 'only one value' is nearly trivial and the enum syntax sugar doesn't give you that much. You can't switch on it anymore, that's about it. When you handroll the enum type, you can get around the 'no generics' restriction.

public final class Settings<T> {
 private final String path;
 private final Class<T> type;

 public static final EXAMPLE_1 = new Settings<Integer>("path here", Integer.class);

 private Settings(String path, Class<T> type) {
   // private constructor - ensuring nobody can make them but this source file
   this.type = type;
   this.path = path;
 }

 public Class<T> getClassType() { return this.type; }
 public String getPath() { return this.path; }
 public Setting<T> getSetting() {
   return new Setting<T>(...);
 }
}

You can still write Settings.EXAMPLE_1 the same way you'd do if Settings is an enum.

Related