Monday, July 09, 2007

More with Java enums: simulating inheritance

One drawback to the Java enum is the lack of inheritance. Java uses inheritance for many reasons, one of which is implementation inheritance. (In C++ you would use private inheritance for the same reason.)

To recapture implementation inheritance with enums, use the visitor pattern:

class Convert {
    public interface Vivid {
        char character();
    }

    public static <E extends Enum<E> & Vivid> E from(
            E[] values, char character) {
        for (E e : values)
            if (e.character() == character)
                return e;

        throw new IllegalArgumentException("Boring: " + character);
    }
}

enum Men implements Convert.Vivid {
    OLIVIER('L'), WAYNE('J');

    private final char character;

    Men(char character) {
        this.character = character;
    }

    public char character() {
        return character;
    }

    public static Men valueOf(char character) {
        return Convert.from(values(), character);
    }
}

The implementation of valueOf(char):Men is handled in Convert and can be reused in other enum classes.

7 comments:

hnadra said...

Nice! Thanks a lot!

nodje said...

Great! still very useful after 2 years.

It's actually too bad it doesn't work with a String type instead of character.

I get the compiler error: valueOf(java.lang.String) is already defined. And I don't understand why it can't overload the original Enum.
Isn't it possible to overload static method?

Anyway, no big deal, just have to name the 'valueOf' method something like 'value'.

And it gives me a solution to an old unsolved problem. I'm gonna expose it here, if you don't mind, I think it should be useful to others.

It solves a problem when working with legacy application/databases where you have a lot of String constants and you have to make test on these with values coming from the db.

Here's an exemple for those interested:

enum Model implements StringEnum.StringContent {
MODEL1("long boring name1"), MODEL2("..."), MOdEL3("..."), ...;

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

private final String value;

public String getValue() {
return this.value;
}

//can't be named valueOf, compiler error
public static Model value(String value) {
return StringEnum.from(values(), value);
}
}

This allow, finally, to replace long if conditions that clutter legacy code:

if (MODEL1.equals(modelName) || MODEL2.equals(modelName) ||
MODEL3.equals(modelName) ||
MODEL4.equals(modelName) ||
...
) performSomeStuff();

with

EnumSet<Mode>> subset = EnumSet.of(Model.MODEL1, Model.MODEL2);
if(subset.contains(Model.value("my model name coming from the database")))
performSomeStuff();


You can work with EnumSet methods with values of the Enum and not only with names.

With static import you can reduce the code even more. You can reuse your subsets by putting them outside of the method, give them meaningful names, etc..

Hope it makes sense.

StringEnum.StringContent are the original Convert.Vivid changed to work with Strings. Just replace char with String

Agreeably they could be better named ;)

binkley said...

Yep, String is an issue just as you point out. Pick a better method name than "valueOf" which does not overload (I should have done this in the first place) and you will be good.

Loshia said...

Hi, I wanted to ask where exactly did you implement the VIsitor pattern? All I see is using interfaces to allow enums inheritance.

binkley said...

@Loshia questions about visitor pattern:

Visitor pulls the data structure apart from the algorithm using it.

In this case, Men.valueOf(character):Men lets Convert.from(E[], character):E do the work, providing the E structure to operate over.

Or so it looks to me.

Loshia said...

You can say that, yes. But it's not the typical visitor pattern where you have a Visitor interface with a few implementations that visit an object hierarchy that accepts the Visitor with:

accept(Visitor someVisitor);

Chris May said...

Hi all,

the missing inheritance of enumerations is a recurring (and pretty much arbitrary) limitation of the Java language, and for many instances a royal pain in the neck (especially in combination with annotations). And the forums are full of complaints about this limitation...

We are joining forces now to propose at least a basic support for enumeration extendability for the upcoming Java-8 edition.

Please help us in moving the Extended-Enum petition forward, and vote for us at http://www.extended-enums.org - this would be a minimal change with a maximum of impact and transcendence!

cheers and many thnx,

Chris