How to implement enum with generics?

JavaGenericsInterfaceEnums

Java Problem Overview


I have a generic interface like this:

interface A<T> {
    T getValue();
}

This interface has limited instances, hence it would be best to implement them as enum values. The problem is those instances have different type of values, so I tried the following approach but it does not compile:

public enum B implements A {
    A1<String> {
        @Override
        public String getValue() {
            return "value";
        }
    },
    A2<Integer> {
        @Override
        public Integer getValue() {
            return 0;
        }
    };
}

Any idea about this?

Java Solutions


Solution 1 - Java

You can't. Java doesn't allow generic types on enum constants. They are allowed on enum types, though:

public enum B implements A<String> {
  A1, A2;
}

What you could do in this case is either have an enum type for each generic type, or 'fake' having an enum by just making it a class:

public class B<T> implements A<T> {
    public static final B<String> A1 = new B<String>();
    public static final B<Integer> A2 = new B<Integer>();
    private B() {};
}

Unfortunately, they both have drawbacks.

Solution 2 - Java

As Java developers designing certain APIs, we come across this issue frequently. I was reconfirming my own doubts when I came across this post, but I have a verbose workaround to it:

// class name is awful for this example, but it will make more sense if you
//  read further
public interface MetaDataKey<T extends Serializable> extends Serializable
{
    T getValue();
}

public final class TypeSafeKeys
{
    static enum StringKeys implements MetaDataKey<String>
    {
        A1("key1");

        private final String value;

        StringKeys(String value) { this.value = value; }

        @Override
        public String getValue() { return value; }
    }

    static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2(0);

        private final Integer value;

        IntegerKeys (Integer value) { this.value = value; }

        @Override
        public Integer getValue() { return value; }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

At that point, you gain the benefit of being a truly constant enumeration value (and all of the perks that go with that), as well being an unique implementation of the interface, but you have the global accessibility desired by the enum.

Clearly, this adds verbosity, which creates the potential for copy/paste mistakes. You could make the enums public and simply add an extra layer to their access.

Designs that tend to use these features tend to suffer from brittle equals implementations because they are usually coupled with some other unique value, such as a name, which can be unwittingly duplicated across the codebase for a similar, yet different purpose. By using enums across the board, equality is a freebie that is immune to such brittle behavior.

The major drawback to such as system, beyond verbosity, is the idea of converting back and forth between the globally unique keys (e.g., marshaling to and from JSON). If they're just keys, then they can be safely reinstantiated (duplicated) at the cost of wasting memory, but using what was previously a weakness--equals--as an advantage.

There is a workaround to this that provides global implementation uniqueness by cluttering it with an anonymous type per global instance:

public abstract class BasicMetaDataKey<T extends Serializable>
     implements MetaDataKey<T>
{
    private final T value;

    public BasicMetaDataKey(T value)
    {
        this.value = value;
    }

    @Override
    public T getValue()
    {
        return value;
    }

    // @Override equals
    // @Override hashCode
}

public final class TypeSafeKeys
{
    public static final MetaDataKey<String> A1 =
        new BasicMetaDataKey<String>("value") {};
    public static final MetaDataKey<Integer> A2 =
        new BasicMetaDataKey<Integer>(0) {};
}

Note that each instance uses an anonymous implementation, but nothing else is needed to implement it, so the {} are empty. This is both confusing and annoying, but it works if instance references are preferable and clutter is kept to a minimum, although it may be a bit cryptic to less experienced Java developers, thereby making it harder to maintain.

Finally, the only way to provide global uniqueness and reassignment is to be a little more creative with what is happening. The most common use for globally shared interfaces that I have seen are for MetaData buckets that tend to mix a lot of different values, with different types (the T, on a per key basis):

public interface MetaDataKey<T extends Serializable> extends Serializable
{
    Class<T> getType();
    String getName();
}

public final class TypeSafeKeys
{
    public static enum StringKeys implements MetaDataKey<String>
    {
        A1;

        @Override
        public Class<String> getType() { return String.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2;

        @Override
        public Class<Integer> getType() { return Integer.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

This provides the same flexibility as the first option, and it provides a mechanism for obtaining a reference via reflection, if it becomes necessary later, therefore avoiding the need for instantiable later. It also avoids a lot of the error prone copy/paste mistakes that the first option provides because it won't compile if the first method is wrong, and the second method does not need to change. The only note is that you should ensure that the enums meant to be used in that fashion are public to avoid anyone getting access errors because they do not have access to the inner enum; if you did not want to have those MetaDataKeys going across a marshaled wire, then keeping them hidden from outside packages could be used to automatically discard them (during marshaling, reflectively check to see if the enum is accessible, and if it is not, then ignore the key/value). There is nothing gained or lost by making it public except providing two ways to access the instance, if the more obvious static references are maintained (as the enum instances are just that anyway).

I just wish that they made it so that enums could extend objects in Java. Maybe in Java 9?

The final option does not really solve your need, as you were asking for values, but I suspect that this gets toward the actual goal.

Solution 3 - Java

If JEP 301: Enhanced Enums gets accepted, then you will be able to use syntax like this (taken from proposal):

enum Primitive<X> {
	INT<Integer>(Integer.class, 0) {
		int mod(int x, int y) { return x % y; }
		int add(int x, int y) { return x + y; }
	},
	FLOAT<Float>(Float.class, 0f)  {
		long add(long x, long y) { return x + y; }
	}, ... ;

	final Class<X> boxClass;
	final X defaultValue;

	Primitive(Class<X> boxClass, X defaultValue) {
		this.boxClass = boxClass;
		this.defaultValue = defaultValue;
	}
}

Solution 4 - Java

By using this Java annotation processor https://github.com/cmoine/generic-enums, you can write this:

import org.cmoine.genericEnums.GenericEnum;
import org.cmoine.genericEnums.GenericEnumParam;

@GenericEnum
public enum B implements A<@GenericEnumParam Object> {
    A1(String.class, "value"), A2(int.class, 0);

    @GenericEnumParam
    private final Object value;

    B(Class<?> clazz, @GenericEnumParam Object value) {
        this.value = value;
    }

    @GenericEnumParam
    @Override
    public Object getValue() {
        return value;
    }
}

The annotation processor will generate an enum BExt with hopefully all what you need!

if you prefer you can also use this syntax:

import org.cmoine.genericEnums.GenericEnum;
import org.cmoine.genericEnums.GenericEnumParam;

@GenericEnum
public enum B implements A<@GenericEnumParam Object> {
    A1(String.class) {
        @Override
        public @GenericEnumParam Object getValue() {
            return "value";
        }
    }, A2(int.class) {
        @Override
        public @GenericEnumParam Object getValue() {
            return 0;
        }
    };

    B(Class<?> clazz) {
    }

    @Override
    public abstract @GenericEnumParam Object getValue();
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZhao YiView Question on Stackoverflow
Solution 1 - JavaJornView Answer on Stackoverflow
Solution 2 - JavapickypgView Answer on Stackoverflow
Solution 3 - JavaSledView Answer on Stackoverflow
Solution 4 - JavaChristophe MoineView Answer on Stackoverflow