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


102

これは、候補者がJava言語に追加したいものとして、最近インタビューで尋ねた質問として浮上しました。Javaがジェネリックス具体化していないことは、痛みとして一般的に認識されていますが、候補者がプッシュされたとき、候補者が実際に達成できたことを私に伝えることができませんでした。

当然、生の型はJava(および安全でないチェック)で許可されているため、ジェネリックを破壊しList<Integer>て(たとえば)実際にが含まれている可能性がありますString。タイプ情報を具体化すると、これは明らかに不可能になる可能性があります。しかし、これ以上のものがあるに違いありません

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

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

編集:これに対する迅速な更新は、回答が主に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?

これは、実行時にジェネリック型のクラスを取得できることを意味しますか?(もしそうなら、私には例があります!)
ジェームズB

reifiableジェネリックの要望のほとんどは、主にコレクションでジェネリックを使用し、それらのコレクションを配列のように動作させたい人からのものだと思います。
kdgregory 2009

2
より興味深い質問(私にとって):JavaでC ++スタイルのジェネリックを実装するには何が必要ですか?これは確かにランタイムでは実行できるように見えますが、既存のクラスローダーをすべて壊します(findClass()パラメーター化を無視する必要があるため、defineClass()できませんでした)。そして、私たちが知っているように、下位互換性が最も重要なのは、その力です。
kdgregory 2009

1
実際、Javaは具体化されたジェネリックを非常に制限された方法で提供します。:私はこのSOスレッドで詳細を提供stackoverflow.com/questions/879855/...
リチャード・ゴメス

JavaOne Keynoteは、Java 9が具体化をサポートすることを示しています。
ランギキーン2014年

回答:


81

私がこの「必要性」に出くわした数回から、最終的にはこの構成に要約されます。

public class Foo<T> {

    private T t;

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

}

これはデフォルトのコンストラクターTがあると仮定してC#で機能します。でランタイムタイプ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は、ユースケースと可能性を示しています。


5
これは(たとえば)C#で動作しますか?Tにデフォルトのコンストラクターがあることをどのようにして知っていますか?
Thomas Jung、

4
はい、そうです。そして、これは基本的な例にすぎません(javabeansで作業していると仮定します)。重要な点は、Javaでは実行時T.classまたはによってクラスを取得できないため、T.getClass()そのすべてのフィールド、コンストラクター、およびメソッドにアクセスできるということです。施工も不可能です。
BalusC 2009

3
これは、これはコンストラクタ/パラメータなどの周りのいくつかの非常に脆い反射と組み合わせて有用であることが唯一の可能性があり、特にとして、「大きな問題」として私には本当に弱いようだ
oxbow_lakes

33
タイプを次のように宣言すると、C#でコンパイルされます。public class Foo <T> where T:new()。これにより、Tの有効なタイプがパラメーターなしのコンストラクターを含むタイプに制限されます。
マーティンハリス

3
@Colin実際には、特定のジェネリック型が複数のクラスまたはインターフェースを拡張する必要があることをjavaに伝えることができます。docs.oracle.com/javase/tutorial/java/generics/bounded.htmlの「Multiple Bounds」セクションを参照してください。(もちろん、オブジェクトが、インターフェースに抽象化できるメソッドを共有しない特定のセットの1つになることはわかりません。これは、テンプレートの特殊化を使用してC ++で多少実装できます。)
JAB

99

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

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

5
それを行うために具体化する必要は全くありません。メソッドの選択は、コンパイル時の型情報が利用可能なコンパイル時に行われます。
トム・ホーティン-タックライン2009

26
@Tom:これは型消去のためにコンパイルすらしません。どちらもとしてコンパイルされpublic void my_method(List input) {}ます。しかし、私はこの必要性に出くわしたことはありません。単に彼らが同じ名前を持っていなかったからです。彼らが同じ名前を持っている場合、私はpublic <T extends Object> void my_method(List<T> input) {}より良いアイデアではないかどうか質問します。
BalusC、2009

1
フム、私は完全にパラメータの同じ数の過負荷を回避する傾向があり、のようなものを好むだろうmyStringsMethod(List<String> input)し、myIntegersMethod(List<Integer> input)そのような場合でものためのオーバーロード場合は、Javaで可能でした。
Fabian Steeg、

4
@Fabian:つまり、個別のコードを用意し<algorithm>、C ++で得られるような利点を防ぐ必要があります。
David Thornley、

私は混乱しています、これは本質的に私の2番目のポイントと同じですが、それでも2つの反対票を得ましたか?誰かが私を啓発したいと思いますか?
rsp

34

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

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を使用するかどうか)。


1
はい-これを回避する方法ParametrizedListは、ソースコレクションチェックタイプのデータをコピーするを作成することです。少し似てCollections.checkedListいますが、最初にコレクションをシードできます。
oxbow_lakes 09

@tackline-まあ、いくつかの抽象化はリークが少なくなります。実装で型メタデータにアクセスする必要がある場合は、クライアントがクラスオブジェクトを送信する必要があるため、外部インターフェイスから通知されます。
gustafc 2009

...具体化されたジェネリックスを使用すると、外部インタフェースを変更せずにT.class.getAnnotation(MyAnnotation.class)Tジェネリック型はどこにあるか)などを追加できます。
gustafc 2009

@gustafc:C ++テンプレートによって完全な型安全性が得られると思われる場合は、次をお読みください:kdgregory.com/index.php
page=

@kdgregory:C ++が100%タイプセーフであると言ったことはありません-その消去がタイプセーフに損害を与えるだけです。ご存じのとおり、「C ++には、Cスタイルのポインターキャストと呼ばれる独自の型消去の形式があります。」しかし、Javaは動的キャストのみを行う(再解釈は行わない)ため、具体化すると、この全体が型システムにプラグインされます。
gustafc 2009

26

コードで一般的な配列を作成することができます。

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

Objectの何が問題になっていますか?とにかく、オブジェクトの配列は参照の配列です。これは、オブジェクトデータがスタックに置かれているようなものではなく、すべてヒープ内にあります。
Ran Biron、

19
タイプセーフ。Object []には必要なものを何でも入れることができますが、String []にはストリングのみを入れることができます。
ターナー

3
Ran:皮肉なことをしなくても、Javaの代わりにスクリプト言語を使いたくなるかもしれません。そうすれば、型指定されていない変数をどこにでも持つことができます。
空飛ぶ羊

2
配列は両方の言語で共変です(したがって型保証されていません)。String[] strings = new String[1]; Object[] objects = strings; objects[0] = new Object();両方の言語で正常にコンパイルされます。notsofineを実行します。
Martijn

16

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

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

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

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

1
ほとんどの場合、シリアライゼーションの場合は難しいと思います。シリアル化される一般的なもののクラス型を探りたいと思うことがよくありますが、型の消去のために足りません。このようなことをするのは難しくなりますdeserialize(thingy, List<Integer>.class)
Cogman

3
これが最良の答えだと思います。特に、型の消去のために本当に根本的にどの問題が発生し、Java言語の設計問題だけが何であるかを説明する部分。私が消去消去を始めると私が最初に言うのは、ScalaとHaskellも型消去によって機能しているということです。
aemxdp 2014年

13

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

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

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

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

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

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

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

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


この。これの千倍。Javaで逆シリアル化コードを書き込もうとするたびに、私は他の何かで作業していないことを嘆きながら1日を過ごします
Basic

11

私のJava Geneircsへの露出は非常に限られており、他の回答がすでに言及している点とは別に、モーリスナフタリンとフィリップウォルダーの著書「Java Generics and Collections」で説明されているシナリオがあります。

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

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

class ParametericException<T> extends Exception // compile error

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

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

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

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


1
これは妥当なポイントですが、パラメーター化された例外クラスがなぜ役立つのかはよくわかりません。回答を修正して、これが役立つ場合の簡単な例を含めてください。
oxbow_lakes 2009

@oxbow_lakes:申し訳ありません私のJava Genericsに関する知識は非常に限られており、それを改善しようとしています。そのため、パラメーター化された例外が役立つ例を考えることができません。それについて考えてみます。どうも。
sateesh 2009

例外タイプの多重継承の代わりとして機能する可能性があります。
メリトン2009

パフォーマンスの向上は、現在、型パラメーターはObjectから継承する必要があるため、プリミティブ型のボクシングが必要であり、実行とメモリのオーバーヘッドが発生します。
メリトン2009

8

配列は具体化された場合、おそらくジェネリックスではるかにうまく機能します。


2
もちろん、問題は解決しません。List<String>はありませんList<Object>
トム・ホーティン-タックライン2009

同意する-ただし、プリミティブ(整数、ロングなど)のみ。「通常の」オブジェクトの場合、これは同じです。プリミティブをパラメーター化されたタイプにすることはできないので(はるかに深刻な問題、少なくともIMHO)、これを本当の苦痛とは見なしません。
Ran Biron、

3
配列の問題はそれらの共分散であり、具体化とは何の関係もありません。
2010

5

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

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

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

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


しかし、クラスに合格することは、私にとってはずっと安全に思えます。いずれにしても、データベースクエリから反射的にインスタンスを作成することは、とにかく(コードとデータベースの両方で)リファクタリングなどの変更に対して非常に脆弱です。私はクラスを提供することが不可能であるものを探していたと思います
oxbow_lakes '18 / 12/18

5

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

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


2
これは本質的にnew T[]、Tがプリミティブ型の場所で実行できることを意味します。
oxbow_lakes 2010年

2

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

私の意見では、ジェネリックは単なる余分なキャストであり、冗長なキャストを大幅に削減します。


これは確かにジェネリックがJavaにあるものです。C#やその他の言語では、これらは強力なツールです
基本

1

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

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

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

0

これが今日私を捕まえたものです:具象化せずに、一般的なアイテムの可変引数リストを受け入れるメソッドを作成した場合...

それは起こりそうにないようです?...もちろん、データ型として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で具体化しない場合、これはすべての「クラス」オブジェクトを受け入れます。そしてそれは前のコードのほんの小さな拡張です:

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.