Можно ли расширять enum в Java 8?

Просто играя и придумал приятный способ добавить функциональность enum в метод Java Enum toString() с помощью это.

Некоторые дополнительные манипуляции позволили мне также добавить аккуратный (т.е. не бросать исключение) обратный поиск, но есть проблема. Он сообщает:

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

Есть ли способ?

Цель состоит в том, чтобы тихо добавить (через реализацию интерфейса с помощью метода default, как я добавил politeName в связанном ответе) метод lookup, который выполняет функцию valueOf, не вызывая исключения. Является ли это возможным? Теперь явно можно расширить enum - одну из моих основных проблем с Java до сих пор.

Здесь моя неудачная попытка:

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
}

Ответ 1

Вы слишком усложняете свой дизайн. Если вы согласны с тем, что вы можете вызывать метод default только для экземпляра, весь код может выглядеть следующим образом:

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
}

Вы можете протестировать его с помощью:

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

Альтернативой не бросания/ловушки будет:

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();
}

использовать как:

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

Ответ 2

Случай такой же, как вы не можете создать по умолчанию toString() в интерфейсе. Перечисление уже содержит подпись для статического метода valueOf(String), поэтому вы не можете его переопределить.

Перечисление - это константа времени компиляции, и из-за этого очень сомнительно, что когда-нибудь они будут расширяемы.

Если вы хотите получить константу через имя, вы можете использовать это:

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();

    }

Ответ 3

Здесь, в основном, две точки. В частности, причина, по которой он не компилируется, 8.4.8.1:

Это ошибка времени компиляции, если метод экземпляра переопределяет статический метод.

Другими словами, перечисление не может реализовать HasValue из-за столкновения имени.

Тогда существует более общая проблема, которая заключается в том, что статические методы просто не могут быть "переопределены". Поскольку valueOf - это статический метод, вставленный компилятором в сам класс, полученный из Enum, нет никакого способа его изменить. Мы также не можем использовать интерфейсы для его решения, поскольку у них нет статических методов.

В этом конкретном случае это место, где композиция может сделать такую ​​вещь менее повторяющейся, например:

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);
    }
}

(Плюс, я бы порекомендовал, чтобы все равно перехватывал исключение.)

Я понимаю, что "вы не можете этого сделать" на самом деле не является желательным ответом, но я не вижу пути вокруг него из-за статического аспекта.

Ответ 4

Я думаю, что у меня есть ответ - он взломан и использует отражение, но, похоже, соответствует краткому - то есть обратный поиск без методов в перечислении и исключение исключения.

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()));
    }
}

печатает

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

Добавлено

Вдохновленный @Holger - это то, что я чувствую, больше всего похоже на то, что я искал:

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;
}