Java enumリテラルがジェネリック型パラメーターを持つことができないのはなぜですか?


148

Java列挙型は素晴らしいです。ジェネリックもそうです。もちろん、型消去のために後者の限界を知っています。しかし、理解できないことが1つあります。なぜ、このような列挙型を作成できないのですか。

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

このジェネリック型パラメーター<T>は、さまざまな場所で役立ちます。メソッドへのジェネリック型パラメーターを想像してください:

public <T> T getValue(MyEnum<T> param);

または列挙クラス自体でも:

public T convert(Object o);

より具体的な例#1

上記の例は一部にとって抽象的すぎるように見えるかもしれないので、これを行う理由のより現実的な例を次に示します。この例では、使用したい

  • 列挙型。プロパティキーの有限セットを列挙できるため
  • ジェネリックス。これは、プロパティレベルを格納するためのメソッドレベルの型安全性を持つことができる
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

より具体的な例#2

データ型の列挙があります:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

各列挙リテラルには、明らかにジェネリック型に基づいた追加のプロパティがあります <T>、同時に列挙型(不変、シングルトン、列挙可能など)になります。

質問:

誰もこれを考えなかったのですか?これはコンパイラ関連の制限ですか?キーワード「enum」が構文シュガーとして実装され、JVMに対して生成されたコードを表すという事実を考えると、この制限は理解できません。

誰が私にこれを説明できますか?答える前に、これを考慮してください:

  • 私はジェネリック型が消去されていることを知っています:-)
  • Classオブジェクトを使用する回避策があることを知っています。それらは回避策です。
  • ジェネリック型は、該当する場合(たとえば、convert()メソッドを呼び出すとき)にコンパイラー生成の型キャストになります。
  • ジェネリック型<T>は列挙型にあります。したがって、列挙型の各リテラルによってバインドされます。したがって、コンパイラーは、次のようなものを作成するときに適用するタイプを知っています。String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • T getvalue()メソッドのジェネリック型パラメーターにも同じことが当てはまります。コンパイラは、呼び出し時に型キャストを適用できますString string = someClass.getValue(LITERAL1)

3
私もこの制限を理解していません。私の列挙型にさまざまな「比較可能」タイプが含まれていて、ジェネリックを使用すると、同じタイプの比較可能タイプのみを比較することができます(実行時に適切なタイプが比較される場合でも)。enumでバインドされたタイプを使用して、サポートされている比較可能なタイプを指定することで、これらの警告を取り除くことができましたが、代わりにSuppressWarningsアノテーションを追加する必要がありました-回避策はありません!いずれにしても、compareToはクラスキャスト例外をスローするので、それは問題ないと思いますが、それでも...
MetroidFan2002

5
(+1)私はプロジェクトのタイプセーフティギャップを埋めようとしている最中です。この恣意的な制限によって完全に停止しました。これを考慮してくださいenum。Java1.5より前に使用していた「型保証された列挙型」のイディオムに変換します。突然、列挙型メンバーをパラメーター化できます。それがおそらく私がやろうとしていることです。
Marko Topolnik、2015年

1
@EdwinDalorzo:質問がjOOQの具体例で更新されました。これは、これまでは非常に便利でした。
Lukas Eder

2
@LukasEder私は今あなたの要点を見ています。クールな新機能のように見えます。プロジェクトコインメーリングリストで提案する必要があるかもしれません。列挙型には他にも興味深い提案がありますが、あなたのようなものはありません。
Edwin Dalorzo、2015年

1
完全に同意する。ジェネリックのない列挙型は不自由です。あなたのケース#1も私のものです。ジェネリック列挙型が必要な場合は、JDK5をあきらめて、プレーンな古いJava 1.4スタイルで実装します。このアプローチには追加の利点もあります。1つのクラスまたはパッケージにすべての定数を含める必要はありません。したがって、機能ごとのパッケージスタイルははるかに優れています。これは、構成のような「列挙型」にぴったりです。定数は、論理的な意味に従ってパッケージに分散されます(すべてを表示したい場合は、型階層を示しています)。
トマーシュZáluský

回答:


50

これは現在、JEP-301拡張列挙型の時点で議論されています。JEPで示されている例は、まさに私が探していたものです。

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

残念ながら、JEPは依然として重大な問題に取り組んでいます:http : //mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html


2
2018年12月の時点で、JEP 301の周りには再びいくつかの兆候がありましたが、その議論をざっと見てみると、問題がまだ解決されていないことは明らかです。
ポン

11

答えは質問です:

タイプ消去のため

引数の型が消去されるため、これら2つの方法はいずれも使用できません。

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

ただし、これらのメソッドを実現するには、列挙型を次のように作成します。

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}

2
まあ、消去はコンパイル時に行われます。ただし、コンパイラは型チェックにジェネリック型情報を使用できます。そして、ジェネリック型を型キャストに「変換」します。私は質問を言い換えます
Lukas Eder

2
私が完全にフォローしているのかわかりません。取るpublic T convert(Object);。このメソッドは、たとえば、さまざまな型の束を、たとえばStringである<T>に絞り込むことができると思います。Stringオブジェクトの構築はランタイムです-コンパイラは何もしません。したがって、String.classなどのランタイムタイプを知っておく必要があります。それとも何か不足していますか?
Martin Algesten、

6
ジェネリック型<T>が列挙型(またはその生成クラス)にあるという事実を見逃していると思います。enumの唯一のインスタンスはリテラルであり、すべて定数のジェネリック型バインディングを提供します。したがって、パブリックT convert(Object)のTについてはあいまいさがありません。
Lukas Eder

2
ああ!とった。あなたは私より
賢い

1
他のすべてのものと同様の方法で評価される列挙型のクラスに起因すると思います。Javaでは、物事一定しているように見えますが、そうではありません。public final static String FOO;静的ブロックについて検討してくださいstatic { FOO = "bar"; }-コンパイル時に既知であっても定数は評価されます。
Martin Algesten

5

できないから。真剣に。言語仕様に追加することができます。それはされていません。多少複雑になります。コストに対するその利点は、それが高い優先順位ではないことを意味します。

更新:現在、JEP 301:Enhanced Enumsで言語に追加されています。


合併症について詳しく教えてもらえますか?
Mr_and_Mrs_D 2014

4
私はこれについて数日間考えてきましたが、まだ合併症は見られません。
Marko Topolnik、2015年

4

ENUMには機能しない他のメソッドがあります。何だろうMyEnum.values()返りますか?

どうですか MyEnum.valueOf(String name)ですか?

valueOfについては、コンパイラーが次のようなジェネリックメソッドを作成できると考えている場合

public static MyEnum valueOf(String name);

のように呼び出すにはMyEnum<String> myStringEnum = MyEnum.value("some string property")、それも機能しません。たとえば、あなたが電話しMyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")たら?たとえばMyEnum.<Int>value("some double property")、型の消去のため、例外をスローしたり、呼び出し時にnullを返したりするなど、そのメソッドを正しく機能させることはできません。


2
なぜ機能しないのですか?彼らは単純にワイルドカードを使用します...:MyEnum<?>[] values()およびMyEnum<?> valueOf(...)
Lukas Eder

1
しかしMyEnum<Int> blabla = valueOf("some double property");、型に互換性がないため、このような割り当てを行うことができませんでした。また、その場合は、nullを取得する必要があります。これは、doubleプロパティ名に存在しないMyEnum <Int>を返したいためであり、消去のためにそのメソッドを適切に機能させることができないためです。
user1944408 2015

また、values()をループ処理する場合は、MyEnum <?>を使用する必要があります。これは、たとえばIntプロパティのみをループ処理することはできないため、通常は必要ありません。また、回避したい多くのキャストを行う必要があります。すべての型に異なる列挙型を作成するか、インスタンスを使用して独自のクラスを作成することをお勧めします...
user1944408

ええと、私はあなたが両方を持つことはできないと思います。通常、私は自分の「列挙型」実装に頼っています。それだけEnumで他にも多くの便利な機能があり、これもオプションで非常に便利です...
Lukas Eder

0

率直に言って、これは何よりも問題を探す解決策のようです。

java enumの全体的な目的は、類似のプロパティを共有する型インスタンスの列挙をモデル化して、同等の文字列または整数表現よりも一貫性と豊富さを提供することです。

教科書の列挙型の例を見てみましょう。これはあまり有用ではなく、一貫性もありません。

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

異なる惑星に異なるジェネリック型変換を持たせたいのはなぜですか?それはどのような問題を解決しますか?言語のセマンティクスを複雑にすることは正当化されますか?この動作が必要な場合、列挙型はそれを達成するための最良のツールですか?

さらに、複雑なコンバージョンをどのように管理しますか?

例えば

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

String Integer名前または序数を指定するのに十分簡単です。ただし、ジェネリックスを使用すると、任意のタイプを提供できます。への変換をどのように管理しMyComplexClassますか?ここで、ジェネリック列挙型に提供できる型のサブセットが限られていることをコンパイラーに知らせ、2人の構成体をいじくり回して、多くのプログラマーからはすでに見えなくなっている概念(Generics)に混乱をもたらします。


17
それが役に立たないであろういくつかの例を考えることは、それが決して役に立たないであろうという恐ろしい議論です。
エリアスヴァシレンコ2013

1
例はその点をサポートしています。列挙型のインスタンスは、タイプ(ニースでシンプル)のサブクラスであり、ジェネリックスを含めることは、非常に曖昧な利点のために複雑さの観点からワームの缶です。私に反対票を投じる場合は、あまり多くの言葉で同じことを言っていないTom Hawtinにも反対票を投じてください
nsfyn55

1
@ nsfyn55 Enumは、Javaの最も複雑で最も不思議な言語機能の1つです。たとえば、静的メソッドを自動生成する他の単一の言語機能はありません。さらに、各列挙型は、すでにあるジェネリック型のインスタンス。
Marko Topolnik、2015年

1
@ nsfyn55設計目標は、列挙型メンバーを強力かつ柔軟にすることでした。そのため、カスタムインスタンスメソッドや可変インスタンス変数などの高度な機能をサポートしています。彼らは複雑な使用シナリオでアクティブな協力者として参加できるように、各メンバーに個別に特化した動作を持つように設計されています。何よりも、Java enumは、一般的な列挙型イディオムの型を安全にするように設計されていますが、型パラメーターがないため、残念ながらその最も重要な目標には達していません。それには非常に具体的な理由があったと私は確信しています。
Marko Topolnik、2015年

1
私はあなたが反変について言及していることに気づきませんでした... Java それをサポートします。それは使用サイトだけなので、少し扱いに​​くくなります。interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }どのタイプが消費され、どのタイプが生成されるかを毎回手動で計算し、それに応じて境界を指定する必要があります。
Marko Topolnik、2015年

-2

「enum」は列挙の省略形であるためです。これは、コードを読みやすくするために序数の代わりに使用される名前付き定数のセットです。

型パラメーター化された定数の意図された意味が何であるかはわかりません。


1
java.lang.Stringpublic static final Comparator<String> CASE_INSENSITIVE_ORDER。このように?:-)
Lukas Eder

4
型パラメーター化された定数の意味が何であるかわからないと言っていました。そこで、型パラメーター化された定数を示しました(関数ポインターではありません)。
Lukas Eder

もちろん、Java言語は関数ポインタなどを認識していないため、そうではありません。それでも、定数はパラメーター化された型ではなく、型です。また、型パラメーター自体は定数であり、型変数ではありません。
インゴ

しかし、enum構文は単なる構文上の砂糖です。その下では、それらはまったく同じCASE_INSENSITIVE_ORDERです... Comparator<T>列挙型の場合、関連付けられたバインドされたリテラルがないのはなぜ<T>ですか?
Lukas Eder

Comparator <T>が列挙型である場合、実際にはあらゆる種類の奇妙なものが存在する可能性があります。おそらくInteger <T>のようなものでしょう。しかし、そうではありません。
インゴ

-3

基本的に列挙型はインスタンス化できないので

JVMが許可した場合、Tクラスをどこに設定しますか?

列挙は、常に同じであるか、少なくとも動的に変化しないことが想定されているデータです。

新しいMyEnum <>()?

それでも、次のアプローチは有用かもしれません

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}

8
setO()上の方法が好きかどうかわからないenum。私は列挙定数を検討し、それは不変を意味します。だからそれが可能だとしても、私はそれをしません。
Martin Algesten、

2
列挙型は、コンパイラによって生成されたコードでインスタンス化されます。各リテラルは、実際にはプライベートコンストラクターを呼び出すことによって作成されます。したがって、コンパイル時にジェネリック型をコンストラクターに渡す機会があります。
Lukas Eder

2
列挙型のセッターは完全に正常です。特に、enumを使用してシングルトンを作成している場合(Item 3 Effective Java by Joshua Bloch)
Preston
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.