1

I have two enums that implement a common interface. I've tried to declare the interface so only enums can implement it.

interface CommonEnumInterface<E extends Enum<?>> {

    /** It doesn't matter what this does. */
    E commonInterfaceMethod();

}

enum Enum1 implements CommonEnumInterface<Enum1> {
    FOO,
    BAR,
    BAZ;

    @Override
    public Enum1 commonInterfaceMethod() {
        return null;
    }
}

enum Enum2 implements CommonEnumInterface<Enum2> {
    BLAH,
    YADDA,
    RHUBARB;

    @Override
    public Enum2 commonInterfaceMethod() {
        return Enum2.BLAH;
    }
}

Now I want to create another enum, where each constant has its own Set of elements of type CommonEnumInterface, but not necessarily the same class. Every way I've found feels like a hack, though, especially for cases where I want each constant to declared with more than one set of elements.

These comments show what I've tried. Is there a more elegant way (that compiles)?

enum CreatingAnotherEnumAttempt {

    // Type mismatch: cannot convert from List<Enum<?>&CommonEnumInterface<?>> to List<CommonEnumInterface<?>>
    A(Arrays.asList(Enum1.FOO, Enum2.BLAH)),

    // Cannot cast from List<Enum<?>&CommonEnumInterface<?>> to List<CommonEnumInterface<?>>
    B((List<CommonEnumInterface<?>>) Arrays.asList(Enum1.FOO, Enum2.BLAH)),

    // This works, but it generates type safety warnings, and it's hard to read.
    C(Arrays.asList((CommonEnumInterface<?>) Enum1.FOO, (CommonEnumInterface<?>) Enum2.BLAH)),

    // This is the best I've come up with.
    D(getMixOfElements(Enum1.FOO, Enum2.BLAH));

    /**
     * I want to populate this set in the constructor.
     */
    private final Set<CommonEnumInterface<?>> mixOfElements;

    /**
     * I tried declaring the Set this way, but wildcards can extend only one interface, so there's a syntax error at the ampersand.
     */
    private final Set<? extends Enum<?> & CommonEnumInterface<?>> mixOfElements;

    /**
     * This is the constructor I'm trying to satisfy.
     *
     * @param mixOfElements I could make this a varargs parameter, but that only works for a single set of elements. What if I want more?
     */
    private CreatingAnotherEnumAttempt(List<CommonEnumInterface<?>> mixOfElements) {
        this.mixOfElements = Collections.unmodifiableSet(new HashSet<CommonEnumInterface<?>>(mixOfElements));
    }

    /**
     * This method only exists to allow method {@link #D}. It's pretty pointless, otherwise.
     *
     * @param mixOfElements the mix of elements
     * @return the mix of elements as a list
     */
    private static List<CommonEnumInterface<?>> getMixOfElements(CommonEnumInterface<?>... mixOfElements) {
        return Arrays.asList(mixOfElements);
    }

    /**
     * This works without type safety warnings or superfluous methods, but for my constructor, I need a one-liner.
     */
    private static void wayThatWorksInline() {
        Collection<CommonEnumInterface<?>> inlineWay = new ArrayList<CommonEnumInterface<?>>();
        inlineWay.add(Enum1.FOO);
        inlineWay.add(Enum2.BLAH);
    }

}

My question is above, and in the code comments. Generally, is this the right way to declare my interface?

Michael Scheper
  • 6,514
  • 7
  • 63
  • 76
  • Any reason for the requirement that it be enums? Couldn't you just use classes with public static final members? – Jason Jan 10 '14 at 03:34
  • @Jason: Thanks for encouraging me to take a step back, but in practice, yes, there are a lot of reasons I'm using enums. Some of them are mentioned here: http://stackoverflow.com/questions/9969690/whats-the-advantage-of-a-java-enum-versus-a-class-with-public-static-final-fiel – Michael Scheper Jan 10 '14 at 03:44
  • Good, well-written question by the way +1 – Paul Bellora Jan 10 '14 at 05:46
  • @Paul: Cheers. I was hoping I'd at least get some credit for that, but your answer is the real win. :-) – Michael Scheper Jan 10 '14 at 05:48

2 Answers2

1

The fact that enums are involved is actually a diversion, since simply passing a List<CommonEnumInterface<?>> into the constructor is your goal. To achieve that, do the following:

A(Arrays.<CommonEnumInterface<?>>asList(Enum1.FOO, Enum2.BLAH)),

//etc.

The problem was type inference - sometimes it gets a little too eager. Based on the arguments Enum1.FOO and Enum2.BLAH to asList, the compiler inferred Enum<?>&CommonEnumInterface<?> for T, and so a List<Enum<?>&CommonEnumInterface<?>> was returned. Since generics aren't covariant, this wasn't assignable to List<CommonEnumInterface<?>>. Specifying the type argument for T manually solves this issue.

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
-2

I will try and give this a shot. You can try using the dynamic type.

List<CommonEnumInterface<dynamic>>
or
List<dynamic>

Or go with a more noobish but clean solution

public class MyEnums
{
    List<Tuple<Enum, string>> myEnums;
    public void addEnum(Enum Enum, string EnumName)
    {
        myEnums.Add(new Tuple<Enum, string>(Enum, EnumName));
    }

    public List<Tuple<Enum, string>> getEnums()
    {
        return myEnums;
    }

    public Enum getEnumByName(string EnumName)
    {
        return myEnums.Where(n => n.Item2 == EnumName).Select(n => n.Item1).FirstOrDefault();
    }
}

or

public class myEnums
{
    public dynamic s { get; set; }
}
Pierre
  • 8,397
  • 4
  • 64
  • 80