Vraag Is het mogelijk om enum uit te breiden in Java 8?


Gewoon spelen en bedacht een leuke manier om functionaliteit toe te voegen enumzonde Java Enum toString () methode met deze.

Nog wat sleutelen stond me toe bijna voeg ook een nette (d.w.z. geen uitzondering) reverse look-up toe, maar er is een probleem. Het rapporteert:

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

Is er een manier?

Het doel is hier om stil toe te voegen (via een interface-implementatie met een default methode zoals ik heb toegevoegd politeName in het gekoppelde antwoord) a lookup methode die het valueOf functie zonder een uitzondering te gooien. Is dit mogelijk? Het is duidelijk nu mogelijk om uit te breiden enum - Tot nu toe een van mijn grootste problemen met Java.

Dit is mijn mislukte poging:

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
}

12
2018-02-28 00:22


oorsprong


antwoorden:


U maakt uw ontwerp te ingewikkeld. Als je bereid bent om dat te accepteren, kun je a default methode alleen op een exemplaar, daar ziet de volledige code er als volgt uit:

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
}

Je kunt het testen met:

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

Een niet-werp / vangst alternatief zou zijn:

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

gebruiken zoals:

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

13
2018-02-28 17:40



Hier zijn er eigenlijk twee punten. Specifiek de reden dat het niet compileert is 8.4.8.1:

Het is een compileerfout als een instantiemethode een statische methode opheft.

Met andere woorden, een enum kan HasValue niet implementeren vanwege de naam clash.

Dan is er het meer algemene probleem dat we hebben, namelijk dat statische methoden gewoon niet kunnen worden 'overruled'. Sinds valueOf is een statische methode ingevoegd door de compiler op de Enum-afgeleide klasse zelf, er is geen manier om het te veranderen. We kunnen ook geen interfaces gebruiken om het op te lossen, omdat ze geen statische methoden hebben.

In dit specifieke geval is het een plek waar compositie dit soort dingen kan maken minder repetitief, bijvoorbeeld:

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, ik zou het toch aanbevelen om de uitzondering te vangen.)

Ik besef "je kunt het niet doen" is niet echt een wenselijk antwoord, maar ik zie er geen manier omheen vanwege het statische aspect.


0
2018-02-28 00:55



De zaak is hetzelfde als u geen standaard kunt maken toString() in interface. Het enum bevat al een handtekening voor statische elektriciteit valueOf(String) methode, daarom kunt u deze niet overschrijven.

Het enum is compile time constant en daarom is het echt twijfelachtig of ze ooit nog uitbreidbaar zijn.

Als je de constante via naam wilt krijgen, kun je dit gebruiken:

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
2018-02-28 00:31



Ik denk dat ik een antwoord heb - het is hacky en gebruikt reflectie, maar het lijkt te passen in de opdracht, d.w.z. reverse lookup zonder methoden in de enum en zonder uitzondering te werpen.

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

Toegevoegd

Geïnspireerd door @Holger - dit is wat ik het gevoel heb het meest lijkt op wat ik zocht:

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
2018-02-28 01:25