web-dev-qa-db-ja.com

なぜJavaのジェネリックの実装が悪いと主張する人がいますか?

ジェネリックでは、Javaが正しくなかったということを時折耳にしました。(最も近いリファレンス、 here

私の不慣れを許してください、しかし、何が彼らをより良くしたでしょうか?

112
sdellysse

悪い:

  • 型情報はコンパイル時に失われるため、実行時には、どのタイプが「意味」であるかを判断できません
  • 値型には使用できません(これは大したことです。NETではList<byte>は実際にbyte[]たとえば、ボクシングは不要です)
  • ジェネリックメソッドサック(IMO)を呼び出すための構文
  • 制約の構文は混乱を招く可能性があります
  • ワイルドカードは一般的に紛らわしい
  • 上記によるさまざまな制限-キャストなど

良い:

  • ワイルドカードを使用すると、呼び出し側で共分散/共分散を指定できます。これは多くの状況で非常に適切です。
  • 何もないよりはましです!
143
Jon Skeet

最大の問題は、Javaジェネリックはコンパイル時のみのものであり、実行時にそれを破壊することができます。 この投稿 での良い議論であり、他の議論へのリンクです。

26
Paul Tomblin

主な問題は、Javaが実行時に実際にジェネリックを持たないことです。これはコンパイル時の機能です。

Javaでジェネリッククラスを作成すると、 "Type Erasure"というメソッドを使用して、クラスからすべてのジェネリックタイプを実際に削除し、基本的にそれらをObjectに置き換えます。コンパイラは、メソッド本体に出現するたびに、指定されたジェネリック型にキャストを挿入するだけです。

これには多くの欠点があります。最大の1つ、IMHOは、リフレクションを使用してジェネリック型を検査できないことです。型は実際にはバイトコードではジェネリックではないため、ジェネリックとして検査することはできません。

ここの違いの素晴らしい概要: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html

17
JaredPar
  1. ランタイム実装(つまり、型消去ではありません);
  2. プリミティブ型を使用する機能(これは(1)に関連しています);
  3. ワイルドカードは便利ですが、構文と使用するタイミングを知ることは多くの人を困らせるものです。そして
  4. パフォーマンスの改善はありません((1); Javaジェネリックはcastiオブジェクトの構文糖であるため)。

(1)非常に奇妙な振る舞いにつながります。私が考えることができる最良の例は次のとおりです。仮定:

_public class MyClass<T> {
  T getStuff() { ... }
  List<String> getOtherStuff() { ... }
}
_

次に、2つの変数を宣言します。

_MyClass<T> m1 = ...
MyClass m2 = ...
_

getOtherStuff()を呼び出します:

_List<String> list1 = m1.getOtherStuff(); 
List<String> list2 = m2.getOtherStuff(); 
_

2番目は、パラメーター化された型と関係するnothingがありますが、生の型であるため(パラメーター化された型が提供されないことを意味する)、コンパイラーによってジェネリック型引数が取り除かれます。

また、JDKからのお気に入りの宣言についても触れます。

_public class Enum<T extends Enum<T>>
_

ワイルドカード(混合バッグ)は別として、.Netジェネリックが優れていると思います。

14
cletus

私は本当に物議を醸す意見を捨てるつもりです。ジェネリックは言語を複雑にし、コードを複雑にします。たとえば、文字列を文字列のリストにマップするマップがあるとします。昔は、これを単純に次のように宣言できました。

Map someMap;

今、私はそれを宣言する必要があります

Map<String, List<String>> someMap;

そして、それを何らかのメソッドに渡すたびに、その大きな長い宣言を繰り返し繰り返さなければなりません。私の意見では、余分なタイピングはすべて開発者の注意をそらし、「ゾーン」から抜け出します。また、コードが大量のクラフで満たされている場合、後で戻って重要なロジックを見つけるためにすべてのクラフをすばやくふるい分けるのが難しい場合があります。

Javaはすでに最も一般的な言語の中で最も冗長な言語の1つであるという評判が悪く、ジェネリックはその問題に追加するだけです。

そして、あなたはそのすべての余分な冗長性のために本当に何を買いますか?誰かが文字列を保持するはずのコレクションに整数を入れたり、整数のコレクションから文字列を引き出しようとしたりしたときに実際に何回問題がありましたか?コマーシャルの構築で働いた10年の経験でJavaアプリケーション、これは決して大きなエラーの原因ではありませんでした。 。それは本当に余分な官僚的な手荷物として私を打つだけです。

今、私は本当に物議を醸すつもりです。 Java 1.4のコレクションの最大の問題として見ているのは、どこでも型キャストする必要性です。これらの型キャストは、ジェネリックと同じ問題の多くを持つ余分で冗長な問題だと考えています。例、私はできない

List someList = someMap.get("some key");

私がしなければなりません

List someList = (List) someMap.get("some key");

もちろん、その理由は、get()がListのスーパータイプであるObjectを返すためです。そのため、型キャストなしで割り当てを行うことはできません。繰り返しになりますが、そのルールがどれだけあなたを買うかを考えてください。私の経験からは、それほどではありません。

Javaの方がずっと良かったと思います。実行時に不正なキャストをキャッチします。そうすれば、定義が簡単になります

Map someMap;

そして後で

List someList = someMap.get("some key");

不要なものはすべてなくなり、コードにバグの大きな新しいソースを導入することはないと思います。

9
Clint Miller

実行時ではなくコンパイル時である別の副作用は、ジェネリック型のコンストラクターを呼び出せないことです。したがって、汎用ファクトリを実装するためにそれらを使用することはできません...


   public class MyClass {
     public T getStuff() {
       return new T();
     }
    }

--jeffk ++

8
jdkoftinoff

Javaジェネリックはコンパイル時に正しいかどうかがチェックされ、すべてのタイプ情報が削除されます(プロセスはtype erasureと呼ばれます。したがって、ジェネリックList<Integer>は、rawタイプ、非ジェネリックListに縮小され、任意のクラスのオブジェクトを含めることができます。

これにより、実行時に任意のオブジェクトをリストに挿入できるようになります。また、どのタイプがジェネリックパラメーターとして使用されたかを知ることができなくなりました。後者は、結果的に

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
  System.out.println("Equal");
6
Anton Gogolev

型全体の消去の混乱を無視すると、指定されたジェネリックは機能しません。

これはコンパイルします:

List<Integer> x = Collections.emptyList();

しかし、これは構文エラーです。

foo(Collections.emptyList());

Fooは次のように定義されます:

void foo(List<Integer> x) { /* method body not important */ }

そのため、式のタイプをチェックするかどうかは、ローカル変数に割り当てられているか、メソッド呼び出しの実際のパラメーターに割り当てられているかによって異なります。それはどれほどクレイジーですか?

5
Nat

Javaへのジェネリックの導入は、アーキテクトが機能、使いやすさ、およびレガシーコードとの下位互換性のバランスをとろうとしていたため、困難な作業でした。

Javaのジェネリックの実装が言語の複雑さを許容できないレベルにまで高めたと感じる人もいます(Ken Arnoldの「 Generics Thoughd Harmful 」を参照)。 Angelika Langerの Generics FAQs は、物事がどのように複雑になるかに関してかなり良い考えを与えてくれます。

4
Zach Scrivena

これがウィキであり、他の人に追加できるといいのですが...

問題点:

  • タイプ消去(ランタイムの可用性なし)
  • プリミティブ型はサポートされていません
  • アノテーションとの非互換性(これらは両方とも1.5で追加されたので、機能を急ぐ以外にアノテーションがジェネリックを許可しない理由はまだわかりません)
  • 配列との非互換性。 (時々、本当にClass<?のようなことをしたいのですが、MyObject> []を拡張しますが、許可されていません)
  • ワイルドワイルドカードの構文と動作
  • ジェネリックサポートがJavaクラス間で一貫していないという事実。彼らはほとんどのコレクションメソッドにそれを追加しました。
4
Laplie Anderson

Javaは実行時にジェネリックを強制せず、コンパイル時にのみ強制します。

これは、一般的なコレクションに間違った型を追加するなどの興味深いことを実行できることを意味します。

3
Powerlord

Javaジェネリックはコンパイル時のみであり、非ジェネリックコードにコンパイルされます。 C#では、実際にコンパイルされたMSILは汎用です。 Javaは実行中にまだキャストされます。 詳細はこちらを参照してください

3
Szymon Rozga

Java Posse#279-Joe DarcyとAlex Buckleyへのインタビュー を聞くと、彼らはこの問題について話します。それは、 Reified Generics for Java というタイトルのNeal Gafterブログ投稿にもリンクしています。

多くの人々は、ジェネリックがJavaで実装される方法に起因する制限に満足していません。具体的には、ジェネリック型パラメーターが具体化されていないことに不満を抱いています。実行時に使用できません。ジェネリックは消去を使用して実装され、ジェネリック型パラメーターは実行時に単純に削除されます。

このブログ投稿では、古いエントリ 消去によるパズル:回答セクション を参照しています。これは、要件の移行の互換性についてのポイントを強調しています。

目標は、ソースコードとオブジェクトコードの両方の下位互換性と、移行の互換性を提供することでした。

2
Kevin Hakanson