web-dev-qa-db-ja.com

Javaには具体化されたジェネリックがないことに気を付けなければならないのはなぜですか?

これは、候補者がJava言語に追加したいものとして、最近インタビューで尋ねた質問として浮上しました。 Javaに reified generics がないことは一般的に痛みとして認識されますが、プッシュされたときに、候補者は実際に達成できることを私に伝えることができませんでした彼らはそこにいましたか?.

当然、生の型はJava(および安全でないチェック)で許可されているため、ジェネリックを破壊して、実際にStringsを含む_List<Integer>_になる可能性があります。タイプ情報が具体化されている場合、これは明らかに不可能になる可能性があります。 しかし、これ以上ある必要があります

本当にやりたいことの例の例を投稿できますか?つまり、実行時にListのタイプを取得できることは明らかですが、それをどのように使用しますか?

_public <T> void foo(List<T> l) {
   if (l.getGenericType() == Integer.class) {
       //yeah baby! err, what now?
_

[〜#〜] edit [〜#〜]:回答は主に、パラメータとしてのClass(たとえば、EnumSet.noneOf(TimeUnit.class))。私はの行に沿ってこれが不可能である何かをもっと探していました。例えば:

_List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();

if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
    l1.addAll(l2); //why on earth would I be doing this anyway?
_
101
oxbow_lakes

私がこの「必要性」に遭遇した数回から、最終的には次のような構成に要約されます。

_public class Foo<T> {

    private T t;

    public Foo() {
        this.t = new T(); // Help?
    }

}
_

これは、C#ではTdefaultコンストラクターがあると想定して機能します。 typeof(T) でランタイムタイプを取得し、 Type.GetConstructor() でコンストラクタを取得することもできます。

一般的なJavaソリューションは、_Class<T>_を引数として渡すことです。

_public class Foo<T> {

    private T t;

    public Foo(Class<T> cls) throws Exception {
        this.t = cls.newInstance();
    }

}
_

(コンストラクタの引数として渡す必要は必ずしもありません。メソッドの引数も問題ないため、上記は単なる例であり、_try-catch_は省略のため省略されています)

他のすべてのジェネリック型構成の場合、実際の型は、リフレクションを使用すると簡単に解決できます。以下のQ&Aは、ユースケースと可能性を示しています。

81
BalusC

最も一般的に私に噛み付くのは、複数のジェネリック型にわたる複数のディスパッチを利用できないことです。次のことは不可能であり、それが最良の解決策となる多くの場合があります。

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }
99
RHSeeger

タイプセーフが思い浮かびます。パラメーター化された型へのダウンキャストは、具体化されたジェネリックなしでは常に安全ではありません。

List<String> myFriends = new ArrayList();
myFriends.add("Alice");
getSession().put("friends", myFriends);
// later, elsewhere
List<Friend> myFriends = (List<Friend>) getSession().get("friends");
myFriends.add(new Friend("Bob")); // works like a charm!
// and so...
List<String> myFriends = (List<String>) getSession().get("friends");
for (String friend : myFriends) print(friend); // ClassCastException, wtf!? 

また、抽象化によりリークが少なくなる-少なくとも、型パラメーターに関するランタイム情報に関心がある可能性があるもの。今日、ジェネリックパラメーターの1つのタイプに関するランタイム情報が必要な場合は、Classも渡す必要があります。このように、外部インターフェイスは実装に依存します(パラメーターについてRTTIを使用するかどうか)。

34
gustafc

コードで汎用配列を作成することができます。

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}
26
Turnor

これは古い質問です。答えは山ほどありますが、既存の答えはおかしいと思います。

「具体化された」とは、単に本物を意味し、通常は型消去の反対を意味します。

Java Genericsに関連する大きな問題:

  • この恐ろしいボクシング要件と、プリミティブと参照タイプの間の接続解除。これは具体化や型消去とは直接関係ありません。 C#/ Scalaはこれを修正します。
  • 自己型はありません。このため、JavaFX 8は「ビルダー」を削除する必要がありました。型消去とはまったく関係ありません。 Scalaこれを修正しましたが、C#については不明です。
  • 宣言サイド型の差異はありません。 C#4.0/Scalaにはこれがあります。型消去とはまったく関係ありません。
  • void method(List<A> l)およびmethod(List<B> l)をオーバーロードできません。これは型の消去によるものですが、非常に小さなものです。
  • ランタイム型リフレクションはサポートされていません。これが型消去の中心です。コンパイル時にできるだけ多くのプログラムロジックを検証および証明する超高度なコンパイラーが必要な場合は、リフレクションをできるだけ使用しないでください。このタイプの型消去は気になりません。もっとパッチが多く、スクリプトが多く、動的な型のプログラミングが好きで、可能な限り多くのロジックが正しいことをコンパイラが証明することをあまり気にしない場合は、リフレクションを改善し、型の消去を修正することが重要です。
16
user2684301

シリアライゼーションは具体化によってより簡単になります。私たちが望むのは

deserialize(thingy, List<Integer>.class);

私たちがしなければならないことは

deserialize(thing, new TypeReference<List<Integer>>(){});

醜く見えて、ファンキーに動作します。

次のようなことを言うと本当に役立つ場合もあります

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

これらの物は噛まないことが多いですが、起こると噛みます。

12
Cogman

Java Geneircsへの私の暴露は非常に限られており、他の回答がすでに言及している点を除いて、本で説明されているシナリオがあります Java Generics and Collections、モーリス・ナフタリンとフィリップ・ウォルダーによる、具体化されたジェネリックが有用です。

タイプはreifiableではないので、パラメータ化された例外を持つことはできません。

たとえば、以下のフォームの宣言は無効です。

class ParametericException<T> extends Exception // compile error

これは キャッチ 句は、スローされた例外が特定の型と一致するかどうかを確認します。このチェックは、インスタンステストで実行されるチェックと同じです。また、タイプを再構成できないため、上記の形式のステートメントは無効です。

上記のコードが有効であれば、以下の方法で例外処理が可能でした。

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}

この本では、JavaジェネリックがC++テンプレートの定義(拡張)と同様に定義されている場合)は、最適化の機会が増えるため、より効率的な実装につながる可能性があることも述べています。これ以上の説明があるので、知識のある人からの説明(ポインタ)が役に立ちます。

11
sateesh

配列は具体化された場合、おそらくジェネリックスとの相性がはるかに良くなります。

8
Hank Gay

プリミティブ(値)型のボックス化を回避することは、すばらしいことです。これは、他の人が提起した配列の不満にいくらか関係しており、メモリの使用が制限されている場合は、実際に大きな違いが生じる可能性があります。

パラメータ化された型を反映できることが重要であるフレームワークを作成する場合、いくつかの種類の問題もあります。もちろん、これは実行時にクラスオブジェクトを渡すことで回避できますが、これによりAPIが不明瞭になり、フレームワークのユーザーに追加の負担がかかります。

5
kvb

イテレータとしてjdbc結果セットを提示するラッパーがあります(つまり、依存関係の注入により、データベースから発生した操作をユニットテストするのがはるかに簡単になります)。

APIはIterator<T>のように見えます。Tは、コンストラクターで文字列のみを使用して構築できる型です。次に、イテレーターはsqlクエリから返される文字列を調べ、それをT型のコンストラクターと照合しようとします。

ジェネリックが実装されている現在の方法では、結果セットから作成するオブジェクトのクラスも渡す必要があります。私が正しく理解していれば、ジェネリックスが具体化されていれば、T.getClass()を呼び出してコンストラクターを取得するだけでよく、Class.newInstance()の結果をキャストする必要がなく、はるかに優れています。

基本的には、アプリケーションから作成するのではなく、APIを作成する方が簡単だと思います。オブジェクトから多くのことを推測できるため、構成が少なくて済みます...アノテーションの影響を理解するまでは、それらがconfigの連の代わりに春やxstreamのようなもので使用されているのを見ました。

5
James B

特別なことを成し遂げるわけではありません。理解するのは簡単です。型の消去は初心者にとっては困難なことのように思われ、最終的にはコンパイラーの動作方法を理解する必要があります。

私の意見では、ジェネリックは単純に追加であり、冗長なキャストを大幅に節約できます。

2
Bozho

ここですべての回答が見落としていることの1つは、タイプが消去されるため、ジェネリックインターフェイスを2回継承できないことです。これは、きめの細かいインターフェースを作成するときに問題になる可能性があります。

    public interface Service<KEY,VALUE> {
           VALUE get(KEY key);
    }

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!
1
ExCodeCowboy

これが今日私を捕まえたものです:具象化せずに、ジェネリックアイテムの可変引数リストを受け入れるメソッドを作成する場合...呼び出し元は、それらがタイプセーフであると考えることができますが、誤って古いクラッドを渡して、メソッドを爆破します。

それは起こりそうにないようです? ...もちろん、データ型としてClassを使用するまでは...この時点で、呼び出し元は多くのClassオブジェクトを喜んで送信しますが、単純なタイプミスはTに準拠していないClassオブジェクトを送信し、災害が発生します。

(注:ここで間違えた可能性がありますが、「ジェネリック変数」についてグーグルで検索すると、上記は期待どおりのようです。これを実用的な問題にしているのは、クラスの使用だと思います-呼び出し元は慎重に:()


たとえば、Classオブジェクトをマップのキーとして使用するパラダイムを使用しています(単純なマップよりも複雑ですが、概念的にはそうなっています)。

例えばこれはJava Generics(些細な例))でうまく機能します:

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType)
    {
        // find the entities that are mapped (somehow) from that class. Very type-safe
    }

例えばJava Genericsの具体化なしで、これはANY "Class"オブジェクトを受け入れます。そして、それは前のコードのほんの小さな拡張です:

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType )
    {
        // find the entities that are mapped (somehow) to ALL of those classes
    }

上記の方法は、個々のプロジェクトで何千回も書かれる必要があるため、人的ミスの可能性が高くなります。間違いのデバッグは「面白くない」ことを証明しています。私は現在、代替案を見つけようとしていますが、あまり望みがありません。

0
Adam