Можно ли расширять 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;
}