72

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

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

これについて何か考えはありますか?

4

4 に答える 4

69

できません。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() {};
}

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

于 2012-07-15T08:24:34.187 に答える
40

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

// 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実装に悩まされる傾向があります。これは、通常、名前などの他の一意の値と結合されているためです。これらの値は、コードベース全体で無意識のうちに類似しているが異なる目的で複製される可能性があります。enum全面的に s を使用することにより、平等は、そのような脆弱な動作の影響を受けない景品となります。

システムなどの主な欠点は、冗長性を超えて、グローバルに一意のキー間で相互に変換するという考え方です (たとえば、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キーごとの )を混在させる傾向があるメタデータ バケットです。

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ように使用されることを意図した s がpublic、内部にアクセスできないためにアクセス エラーが発生しないようにすることenumです。それらをマーシャリングされたワイヤに渡したくない場合は、MetaDataKeyそれらをパッケージの外部から隠しておくことで、それらを自動的に破棄することができます (マーシャリング中に、enumアクセス可能でない場合は、キー/値を無視します)。publicインスタンスにアクセスする 2 つの方法を提供することを除いて、インスタンスをstatic作成することによって得られることも失われることもありませんenum

enums が Java でオブジェクトを拡張できるようにしてくれればよかったのにと思います。多分Java 9で?

値を求めていたので、最後のオプションは実際にはニーズを解決しませんが、これは実際の目標に向かっていると思います。

于 2013-05-15T05:53:13.313 に答える
17

JEP 301: Enhanced Enums承認されると、次のような構文を使用できるようになります (提案から抜粋):

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;
    }
}
于 2018-07-23T13:25:35.133 に答える