web-dev-qa-db-ja.com

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

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

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

この汎用型パラメーター_<T>_は、さまざまな場所で役立つ可能性があります。メソッドへのジェネリック型パラメーターを想像してください:

_public <T> T getValue(MyEnum<T> param);
_

または、enumクラス自体でも:

_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)を呼び出すときに型キャストを適用できます
135
Lukas Eder

これは JEP-301 Enhanced Enums の時点で議論されています。 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

46
Lukas Eder

答えは質問にあります:

型の消去のため

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

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

ただし、これらのメソッドを実現するには、enumを次のように構築できます。

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

    private Class<?> clazz;

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

    ...

}
11
Martin Algesten

できないから。真剣に。これは言語仕様に追加できます。そうではありません。複雑さが増します。費用対効果は、優先度が高くないことを意味します。

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

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を返すなど、正しく動作するようにそのメソッドを実装することはできません。

4
user1944408

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

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

教科書の列挙型の例をご覧ください。これはあまり便利でも一貫性もありません:

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

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

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

例えば

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

StringIntegerで簡単に名前または序数を指定できます。ただし、ジェネリックを使用すると、任意の型を指定できます。 MyComplexClassへの変換をどのように管理しますか?ここで、ジェネリック列挙型に提供できる型の限られたサブセットがあることをコンパイラーに強制し、すでに多くのプログラマーを避けているように思われる概念(ジェネリック)に追加の混乱を導入することにより、コンパイラーに2つの構成要素をいじります。

1
nsfyn55