web-dev-qa-db-ja.com

ジェネリックで列挙型を実装する方法は?

私はこのような一般的なインターフェースを持っています:

interface A<T> {
    T getValue();
}

このインターフェイスのインスタンスは限られているため、列挙値として実装することをお勧めします。問題は、それらのインスタンスが異なるタイプの値を持っているため、次のアプローチを試しましたが、コンパイルされません:

public enum B implements A {
    A1<String> {
        @Override
        public String getValue() {
            return "value";
        }
    },
    A2<Integer> {
        @Override
        public Integer getValue() {
            return 0;
        }
    };
}

これについてのアイデアはありますか?

56
Zhao Yi

できません。 Javaは列挙型定数のジェネリック型を許可しません。列挙型では許可されますが、

public enum B implements A<String> {
  A1, A2;
}

この場合にできることは、ジェネリック型ごとに列挙型を持つか、単にクラスにすることで列挙型を持つ「偽」です。

public class B<T> implements A<T> {
    public static final B<String> A1 = new B<String>();
    public static final B<Integer> A2 = new B<Integer>();
    private B() {};
}

残念ながら、どちらにも欠点があります。

54
Jorn

Java開発者が特定のAPIを設計しているため、この問題に頻繁に出くわします。この投稿に出会ったとき、自分自身の疑問を再確認していましたが、詳細な回避策があります。

// class name is awful for this example, but it will make more sense if you
//  read further
public interface MetaDataKey<T extends Serializable> extends Serializable
{
    T getValue();
}

public final class TypeSafeKeys
{
    static enum StringKeys implements MetaDataKey<String>
    {
        A1("key1");

        private final String value;

        StringKeys(String value) { this.value = value; }

        @Override
        public String getValue() { return value; }
    }

    static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2(0);

        private final Integer value;

        IntegerKeys (Integer value) { this.value = value; }

        @Override
        public Integer getValue() { return value; }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

その時点で、真に一定のenumeration値(およびそれに付随するすべての特典)であるという利点が得られます。また、interfaceの独自の実装であるという利点がありますが、 enumが望むアクセシビリティ。

明らかに、これにより冗長性が追加され、コピー/貼り付けミスの可能性が生じます。 enums publicを作成し、アクセスに追加のレイヤーを追加するだけです。

これらの機能を使用する傾向がある設計は、通常、名前などの他の一意の値と結合されているため、脆弱なequals実装の影響を受ける傾向があります。 enumsをボード全体で使用することにより、平等はこのような脆弱な動作の影響を受けない景品です。

このようなシステムの主な欠点は、冗長性を超えて、グローバルに一意のキー間でやり取りすることです(たとえば、JSONとのマーシャリング)。それらが単なるキーである場合、メモリを浪費するコストで安全に再インスタンス化(複製)できますが、以前は弱点であったものを使用します-equals--を利点として。

これには、グローバルインスタンスごとに匿名型で混乱させることにより、グローバルな実装の一意性を提供する回避策があります。

public abstract class BasicMetaDataKey<T extends Serializable>
     implements MetaDataKey<T>
{
    private final T value;

    public BasicMetaDataKey(T value)
    {
        this.value = value;
    }

    @Override
    public T getValue()
    {
        return value;
    }

    // @Override equals
    // @Override hashCode
}

public final class TypeSafeKeys
{
    public static final MetaDataKey<String> A1 =
        new BasicMetaDataKey<String>("value") {};
    public static final MetaDataKey<Integer> A2 =
        new BasicMetaDataKey<Integer>(0) {};
}

各インスタンスは匿名実装を使用しますが、実装には他に何も必要ないことに注意してください。したがって、{}は空です。これは混乱を招くだけでなく、面倒ですが、インスタンス参照が適切であり、混乱が最小限に抑えられている場合は機能しますが、経験の少ないJava開発者にとってはわかりにくいかもしれません。維持します。

最後に、グローバルな一意性と再割り当てを提供する唯一の方法は、起きていることを少し創造的にすることです。私が見たグローバルに共有されたインターフェイスの最も一般的な使用法は、さまざまなタイプ(キーごとにT)のさまざまな値を混在させる傾向があるMetaDataバケットです。

public interface MetaDataKey<T extends Serializable> extends Serializable
{
    Class<T> getType();
    String getName();
}

public final class TypeSafeKeys
{
    public static enum StringKeys implements MetaDataKey<String>
    {
        A1;

        @Override
        public Class<String> getType() { return String.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2;

        @Override
        public Class<Integer> getType() { return Integer.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

これにより、最初のオプションと同じ柔軟性が得られ、後で必要になった場合にリフレクションを介して参照を取得するメカニズムが提供されるため、後でインスタンス化する必要がなくなります。また、最初の方法が間違っているとコンパイルされず、2番目の方法を変更する必要がないため、最初のオプションが提供する多くのエラーが発生しやすいコピー/貼り付けミスを回避します。唯一の注意点は、内部のenumへのアクセス権がないためアクセスエラーが発生しないように、その方法で使用されるpublicsがenumであることを確認することです。それらのMetaDataKeysをマーシャリングされたワイヤを通過させたくない場合、それらを外部のパッケージから隠しておくと、自動的にそれらを破棄できます(マーシャリング中に、enumがアクセス可能かどうかを反射的に確認します、そうでない場合は、キー/値を無視します)。より明確なpublic参照が維持されている場合(staticインスタンスはそのままであるため)、インスタンスにアクセスする2つの方法を提供することを除いて、enumにすることによって得られるものも失われるものもありません。

enumsがJavaでオブジェクトを拡張できるようにしたかったのです。多分Java 9?

あなたが価値を求めていたので、最後のオプションはあなたのニーズを本当に解決しませんが、私はこれが実際の目標に近づくと思います。

33
pickypg

IfJEP 301:Enhanced Enums 受け入れられる、then次のような構文を使用できます(提案から引用):

enum Primitive<X> {
    INT<Integer>(Integer.class, 0) {
        int mod(int x, int y) { return x % y; }
        int add(int x, int y) { return x + y; }
    },
    FLOAT<Float>(Float.class, 0f)  {
        long add(long x, long y) { return x + y; }
    }, ... ;

    final Class<X> boxClass;
    final X defaultValue;

    Primitive(Class<X> boxClass, X defaultValue) {
        this.boxClass = boxClass;
        this.defaultValue = defaultValue;
    }
}
10
Tobogganski