web-dev-qa-db-ja.com

Java 8で列挙型を拡張することは可能ですか?

ただ遊んで、 Java Enum toString()メソッド with thisenumsに機能を追加するための甘い方法を思いついた。

さらにいじくり回すと、ほぼに、きちんとした(つまり、例外をスローしない)逆ルックアップを追加することもできましたが、問題があります。それは報告しています:

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に関する私の主要な問題の1つです。

これが私の失敗した試みです:

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
}
14
OldCurmudgeon

あなたはあなたのデザインを過度に複雑にしています。インスタンスでのみ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);
16
Holger

ケースは、インターフェースでデフォルトの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();

    }

ここでは、基本的に2つのポイントがあります。具体的には、コンパイルされない理由は 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);
    }
}

(さらに、とにかく例外をキャッチすることをお勧めします。)

「できない」というのはあまり望ましい答えではないのですが、静的な面があるため、回避策がわかりません。

0
Radiodef

私は答えがあると思います-それはハッキーでリフレクションを使用しますが、簡単に合うようです-つまり、列挙型にメソッドがなく、例外をスローしない逆ルックアップです。

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