15

Just playing and came up with a sweet way to add functionality to enums in Java Enum toString() method with this.

Some further tinkering allowed me to nearly also add a tidy (i.e. not throwing an exception) reverse look-up but there's a problem. It's reporting:

error: valueOf(String) in X cannot implement valueOf(String) in HasValue
public enum X implements PoliteEnum, ReverseLookup {
overriding method is static

Is there a way?

The aim here is to silently add (via an interface implementation with a default method like I added politeName in the linked answer) a lookup method that does the valueOf function without throwing an exception. Is it possible? It is clearly now possible to extend enum - one of my major problems with Java until now.

Here's my failed attempt:

public interface HasName {

    public String name();
}

public interface PoliteEnum extends HasName {

    default String politeName() {
        return name().replace("_", " ");
    }
}

public interface Lookup<P, Q> {

    public Q lookup(P p);
}

public interface HasValue {
    HasValue valueOf(String name);
}

public interface ReverseLookup extends HasValue, Lookup<String, HasValue> {

    @Override
    default HasValue lookup(String from) {
        try {
            return valueOf(from);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

}

public enum X implements PoliteEnum/* NOT ALLOWED :( , ReverseLookup*/ {

    A_For_Ism, B_For_Mutton, C_Forth_Highlanders;
}

public void test() {
    // Test the politeName
    for (X x : X.values()) {
        System.out.println(x.politeName());
    }
    // ToDo: Test lookup
}
Community
  • 1
  • 1
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • My intuition here is "add generics". I'm guessing `X.valueOf()` has to return `X`, so the interface `HasValue` should be `interface HasValue` or some such. Mind you this also means you can't further make subclasses of `X`, but then again I'm not sure that's possible in the first place. – millimoose Feb 28 '14 at 00:26
  • 2
    @millimoose - That was my initial thought - sadly the problem is that `valueOf` is `static` so clearly cannot be overridden. I suspect a completely different approach is required. – OldCurmudgeon Feb 28 '14 at 00:31
  • @OldCurmudgeon Yes, it's static so you can't. – Radiodef Feb 28 '14 at 00:33
  • 2
    You're going to be calling `lookup` on an instance? – Sotirios Delimanolis Feb 28 '14 at 00:40
  • @SotiriosDelimanolis - That is a good point. – OldCurmudgeon Feb 28 '14 at 00:47
  • 2
    For what it's worth, I'd just make the above functionality a utility method instead of some mess that requires 3–4 new interfaces. – millimoose Feb 28 '14 at 00:52
  • @millimoose - but we're extending enums here - don't you see how epic this is??? – OldCurmudgeon Feb 28 '14 at 01:30
  • try this. sometime it may be helpful to you http://stackoverflow.com/questions/2642281/is-it-possible-to-extend-java-enums – realProgrammer Feb 28 '14 at 14:18
  • 3
    @OldCurmudgeon I, and I believe most people here, care about Getting Things Done, as opposed to "elegant tricks" that push the boundaries. At least to me, getting the job done cleanly with minimum effort has more value than making this work as an instance method call no matter what. Understand that Java is a language whose design historically deliberately eschews cleverness, higher order programming, etc. If you want to do "magic", a different platform might serve your needs better. – millimoose Mar 01 '14 at 18:44
  • @OldCurmudgeon E.g. in the accepted answer, it bothers me that `lookup()` is an instance method, since its logic does not depend on the state of `this`. I might not like it that in Java the main way of dynamically referencing a class is using a `Class` object and reflection, but them's the breaks, and to me an `EnumUtils.lookup(Class enumClass, String name)` would be the more intuitive API. – millimoose Mar 01 '14 at 18:47
  • @millimoose - I agree with much that you say. In my work I use many enums and each one generally must have some clutter so they implement an interface. I can now roll all of that clutter into one interface and that is a huge win for me. I accept that my situation is less common but when you work with data transformation and transmission this kind of structure is very common. – OldCurmudgeon Mar 01 '14 at 19:24

4 Answers4

17

You are over-complicating your design. If you are willing to accept that you can invoke a default method on an instance only, there entire code may look like this:

interface ReverseLookupSupport<E extends Enum<E>> {
    Class<E> getDeclaringClass();
    default E lookup(String name) {
        try {
            return Enum.valueOf(getDeclaringClass(), name);
        } catch(IllegalArgumentException ex) { return null; }
    }
}
enum Test implements ReverseLookupSupport<Test> {
    FOO, BAR
}

You can test it with:

Test foo=Test.FOO;
Test bar=foo.lookup("BAR"), baz=foo.lookup("BAZ");
System.out.println(bar+"  "+baz);

An non-throwing/catching alternative would be:

interface ReverseLookupSupport<E extends Enum<E>> {
    Class<E> getDeclaringClass();
    default Optional<E> lookup(String name) {
        return Stream.of(getDeclaringClass().getEnumConstants())
          .filter(e->e.name().equals(name)).findFirst();
}

to use like:

Test foo=Test.FOO;
Test bar=foo.lookup("BAR").orElse(null), baz=foo.lookup("BAZ").orElse(null);
System.out.println(bar+"  "+baz);
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 2
    If you don’t like the reflective code/ temporary array you can replace `Stream.of(getDeclaringClass().getEnumConstants())` with `EnumSet.allOf(getDeclaringClass()).stream()`. – Holger Feb 28 '14 at 17:59
  • Now that is much neater than mine!! I like the way you bring in the `getDeclaringClass` and make it specific to enums all in one interface. Clever using the stream - I would probably have streamed them into a `Set` but that is just me. Thank you for the insight. I am looking forward to using Java 8 in ernest. – OldCurmudgeon Feb 28 '14 at 22:01
  • You could stream them into a `Map` using `stream.collect(Collectors.toMap(Enum::name, Function.identity()))`. But I think for typical `enum` sizes, a hash lookup is not faster than a linear search… – Holger Mar 03 '14 at 09:45
  • 1
    It is really annoying that you can not use static methods there. Using an arbitrary value to access class methods is quite strange. – flaschenpost Nov 09 '15 at 15:18
  • @flaschenpost: that’s actually a problem that lacks a real use case. If you know the class of an `enum`, you can simply call `TypeOfEnum.valueOf(name)` anyway. And it’s not that anything was preventing you from putting this stuff into a `static` utility method accepting a `Class` parameter. Making it inherited instance methods was a premise of the question… – Holger Nov 09 '15 at 15:25
  • @Holger the use case is simple: we have numeric values for the enums like 10,20,30 and want to put that stuff (`fromCode(int)`) in a base class/interface. Or we want to write a JPA Mapper for all those enums. Just in my two years of really working with java I often needed to have static methods in a base class. Even when you interoperate with scala you can not write your own "values()" (scala can't see the magically added bytecode at compile time) since it would be static. – flaschenpost Nov 09 '15 at 15:29
  • @Holger don't misunderstand: your solution is cool in the given limitations, I'm not against you, I'm just quite angry about handling static methods so badly from the java guys. – flaschenpost Nov 09 '15 at 15:32
  • @flaschenpost: don’t worry, I don’t feel like having to defend this answer. I just have the feeling, that you have an entirely different question and probably you think there was a limitation in Java which isn’t there. This answer isn’t addressing static methods because the question wasn’t about it. You *can* declare `static` methods in base interfaces, but they require a `Class` parameter in order to provide the right return type. Or, you provide a stub method in each `enum` type delegating to the base methods. – Holger Nov 09 '15 at 15:50
0

The case is the same as you can not create default toString() in interface. The enum already contains signature for static valueOf(String) method therefore you can not override it.

The enum are compile time constant and because of that it really doubtful that they will be extensible someday.

If you want to get the constant via name you can use this:

public static <E extends Enum<E>> Optional<E> valueFor(Class<E> type, String name) {

       return Arrays.stream(type.getEnumConstants()).filter( x ->  x.name().equals(name)).findFirst();

    }
  • I believe you are correct but I sidestepped that in my post on the question by adding a `politeName` method. I'd be happy with some similar outside-the-box ideas. I still need to either call the `enum` `values` or `valueOf` method to get the lookup to work. – OldCurmudgeon Feb 28 '14 at 00:34
  • I do not understand. In the `HasName` you have method `name` that is public and not static that is why it works fine. In `HasValue` you have declaration of `valueOf(String)` witch is static in enum. And tha t generate an error. What I do not get ? – Damian Leszczyński - Vash Feb 28 '14 at 00:44
  • I think you get it all - is there a way that will work? Perhaps we can call `values` through reflection and make a singleton Map - I don't know at this time. – OldCurmudgeon Feb 28 '14 at 00:50
  • You do not need to call it. There is allready something to obtain the constants – Damian Leszczyński - Vash Feb 28 '14 at 01:15
0

Here, there's basically two points. Specifically the reason it doesn't compile is 8.4.8.1:

It is a compile-time error if an instance method overrides a static method.

In other words, an enum can't implement HasValue because of the name clash.

Then there's the more general issue we have which is that static methods just cannot be 'overridden'. Since valueOf is a static method inserted by the compiler on the Enum-derived class itself, there's no way to change it. We also can't use interfaces to solve it since they do not have static methods.

In this specific case it's a place where composition can make this kind of thing less repetetive, for example:

public class ValueOfHelper<E extends Enum<E>> {
    private final Map<String, E> map = new HashMap<String, E>();

    public ValueOfHelper(Class<E> cls) {
        for(E e : EnumSet.allOf(cls))
            map.put(e.name(), e);
    }

    public E valueOfOrNull(String name) {
        return map.get(name);
    }
}

public enum Composed {
    A, B, C;

    private static final ValueOfHelper<Composed> HELPER = (
        new ValueOfHelper<Composed>(Composed.class)
    );

    public static Composed valueOfOrNull(String name) {
        return HELPER.valueOfOrNull(name);
    }
}

(Plus, I'd recommend that over catching the exception anyway.)

I realize "you can't do it" is not really a desirable answer but I don't see a way around it due to the static aspect.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • @Vash What do you mean? I think the OP's trying to circumvent the IllegalArgumentException which some people don't like. This *does* do the same thing as valueOf, it just doesn't throw an exception. This example is more intended to illustrate the composition aspect: other static utilities can be added this way. – Radiodef Feb 28 '14 at 01:15
  • Ok then... i was wonder what OP is up to. But i think that you have understand it correctly. – Damian Leszczyński - Vash Feb 28 '14 at 01:21
  • Just to be sure that you are on the right track, but interfaces in java 8 **do** have the options to have static methods. Not sure whether you are aware of that or not. – skiwi Feb 28 '14 at 10:22
  • @skiwi They do but it seems the static methods aren't "inherited" by implementing classes. Or at least I tried it and it doesn't work so they appear to follow different rules from static methods on a class. – Radiodef Feb 28 '14 at 20:22
  • @Radiodef As far as I recall, static methods are always class specific. That is, with extends/implements they are not becoming available to other classes. You can however still call them ofcourse, via `ClassYouWantToCall.staticMethod()` or in some cases by `super.staticMethod()`. – skiwi Feb 28 '14 at 20:25
  • @skiwi Right, that's why I put "inherited" in quotes. The point is it appears you *can't* call static methods that are on an interface *except* by using the name of the interface class. So here it's not useful. – Radiodef Feb 28 '14 at 20:30
0

I think I have an answer - it's hacky and uses reflection but seems to fit the brief - i.e. reverse lookup without methods in the enum and without throwing exception.

public interface HasName {

    public String name();
}

public interface PoliteEnum extends HasName {

    default String politeName() {
        return name().replace("_", " ");
    }
}

public interface Lookup<P, Q> {

    public Q lookup(P p);
}

public interface ReverseLookup<T extends Enum<T>> extends Lookup<String, T> {

    @Override
    default T lookup(String s) {
        return (T) useMap(this, s);
    }

}

// Probably do somethiong better than this in the final version.
static final Map<String, Enum> theMap = new HashMap<>();

static Enum useMap(Object o, String s) {
    if (theMap.isEmpty()) {
        try {
            // Yukk!!
            Enum it = (Enum)o;
            Class c = it.getDeclaringClass();
            // Reflect to call the static method.
            Method method = c.getMethod("values");
            // Yukk!!
            Enum[] enums = (Enum[])method.invoke(null);
            // Walk the enums.
            for ( Enum e : enums) {
                theMap.put(e.name(), e);
            }
        } catch (Exception ex) {
            // Ewwww
        }
    }
    return theMap.get(s);
}

public enum X implements PoliteEnum, ReverseLookup<X> {

    A_For_Ism,
    B_For_Mutton,
    C_Forth_Highlanders;
}

public void test() {
    for (X x : X.values()) {
        System.out.println(x.politeName());
    }
    for (X x : X.values()) {
        System.out.println(x.lookup(x.name()));
    }
}

prints

A For Ism
B For Mutton
C Forth Highlanders
A_For_Ism
B_For_Mutton
C_Forth_Highlanders

Added

Inspired by @Holger - this is what I feel is most like what I was looking for:

public interface ReverseLookup<E extends Enum<E>> extends Lookup<String, E> {

  // Map of all classes that have lookups.
  Map<Class, Map<String, Enum>> lookups = new ConcurrentHashMap<>();

  // What I need from the Enum.
  Class<E> getDeclaringClass();

  @Override
  default E lookup(String name) throws InterruptedException, ExecutionException {
    // What class.
    Class<E> c = getDeclaringClass();
    // Get the map.
    final Map<String, Enum> lookup = lookups.computeIfAbsent(c, 
              k -> Stream.of(c.getEnumConstants())
              // Roll each enum into the lookup.
              .collect(Collectors.toMap(Enum::name, Function.identity())));
    // Look it up.
    return c.cast(lookup.get(name));
  }

}

// Use the above interfaces to add to the enum.
public enum X implements PoliteName, ReverseLookup<X> {

    A_For_Ism,
    B_For_Mutton,
    C_Forth_Highlanders;
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • The problem is just that the interface is instance only. So you have to do something like `EnumClass.KnownConstant.lookup(unknownName);`. – Radiodef Feb 28 '14 at 01:28
  • @Radiodef - yes - it doesn't really act quite like the static method it is trying to emulate but you have to admit it is an improvement. – OldCurmudgeon Feb 28 '14 at 01:32
  • Way too complicated. Look at my answer… – Holger Feb 28 '14 at 18:01
  • You can change `(E) lookup.get(name)` into `c.cast(lookup.get(name))` to get rid of the type safety warning. – Holger Mar 03 '14 at 09:48
  • 1
    Regarding multi-threading, this looks like a perfect application for [`ConcurrentHashMap.computeIfAbsent(…)`](http://download.java.net/jdk8/docs/api/java/util/concurrent/ConcurrentHashMap.html#computeIfAbsent-K-java.util.function.Function-). This could even simplify the code *and* provide another “Why Java 8 is so cool” example… Btw. note that `collect` returns a new map rather than filling your `HashMap<>`; you don’t need to create a `HashMap` manually here. – Holger Mar 03 '14 at 11:04