Czy można rozszerzyć enum w Javie 8?

Po prostu grałem i wymyśliłem słodki sposób na dodanie funkcjonalności do enum s W Java Enum toString () Metoda z to .

Niektóre dalsze Majsterkowanie pozwoliło mi prawie również dodać schludny (tzn. nie rzucając wyjątku) Reverse look-up, ale jest problem. Pisze:

error: valueOf(String) in X cannot implement valueOf(String) in HasValue
public enum X implements PoliteEnum, ReverseLookup {
overriding method is static
Jest jakiś sposób?

Celem jest tutaj, aby po cichu dodać (za pomocą implementacji interfejsu z metodą default Jak dodałem politeName w linkowanej odpowiedzi) a lookup metoda wykonująca funkcję valueOf bez rzucania wyjątku. Czy to możliwe? Obecnie jest możliwe rozszerzenie enum - do tej pory jeden z moich głównych problemów z Javą.

Oto moja nieudana próba:

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
}
Author: Community, 2014-02-28

4 answers

Zbytnio komplikujesz swój projekt. Jeśli chcesz zaakceptować, że możesz wywołać metodę default tylko na instancji, cały kod może wyglądać tak:

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
}

Możesz go przetestować za pomocą:

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

Alternatywą bez rzucania/łapania byłoby:

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

Używać jak:

Test foo=Test.FOO;
Test bar=foo.lookup("BAR").orElse(null), baz=foo.lookup("BAZ").orElse(null);
System.out.println(bar+"  "+baz);
 14
Author: Holger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-09 15:54:22

Tutaj są dwa punkty. Konkretnie powodem, dla którego nie kompiluje się jest 8.4.8.1:

Jest to błąd w czasie kompilacji, jeśli instancja nadpisuje statyczną metodę.

Innymi słowy, enum nie może zaimplementować HasValue z powodu zderzenia nazw.

Jest jeszcze bardziej ogólny problem, który polega na tym, że statyczne metody po prostu nie mogą być 'nadpisywane'. Ponieważ valueOf jest statyczną metodą wstawianą przez kompilator na klasę pochodną Enum sam , nie da się tego zmienić. Nie możemy również użyć interfejsów do rozwiązania tego problemu, ponieważ nie mają one metod statycznych.

W tym konkretnym przypadku jest to miejsce, w którym kompozycja może sprawić, że tego typu rzeczy będą mniej powtarzalne, na przykład:

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

(poza tym, i tak polecam, aby złapać wyjątek.)

Zdaję sobie sprawę, że "nie możesz tego zrobić" nie jest tak naprawdę pożądaną odpowiedzią, ale nie widzę sposobu na obejście tego ze względu na statyczny aspekt.

 0
Author: Radiodef,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-28 01:34:22

Sprawa jest taka sama, ponieważ nie można utworzyć domyślnego toString() w interfejsie. Enum zawiera już sygnaturę statycznej metody valueOf(String), dlatego nie można jej nadpisać.

Enum są stałą czasową kompilacji i z tego powodu naprawdę wątpliwe, że będą one kiedyś rozszerzalne.

Jeśli chcesz uzyskać stałą poprzez nazwę, możesz użyć tego:

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

    }
 0
Author: Damian Leszczyński - Vash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-28 01:50:34

Myślę, że mam odpowiedź - jest hacky i używa refleksji, ale wydaje się pasować do briefu - tzn. reverse lookup bez metod w enum i bez rzucania WYJĄTKÓW.

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

Druki

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

Dodano

Zainspirowany przez @ Holger - to jest to, co czuję jest najbardziej Jak to, czego szukałem:

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;
}
 0
Author: OldCurmudgeon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-03 11:24:25