Javaのジェネリックの何が問題になっていますか?[閉まっている]


49

このサイトで、Javaによるジェネリックの実装を非難する投稿を何度か見ました。今、私は正直に言って、私はそれらを使用することに何の問題もなかったと言うことができます。しかし、私は自分でジェネリッククラスを作成しようとしませんでした。では、Javaの汎用サポートに関する問題は何ですか?




クリントンビギン(「iBatisの男」)が言ったように、「彼らは機能しません...」
gnat

回答:


54

Javaの一般的な実装では、型消去を使用します。つまり、厳密に型指定されたジェネリックコレクションは、実際にObjectは実行時に型になります。これには、一般的なコレクションに追加するときにプリミティブ型をボックス化する必要があるため、パフォーマンスに関するいくつかの考慮事項があります。もちろん、コンパイル時の型の正確さの利点は、型の消去の一般的な愚かさや、後方互換性への強迫観念よりも重要です。



43
意味new ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
自己への注意-

9
@ジョブはありません。また、C ++は、テンプレートと呼ばれるメタプログラミングに完全に異なるアプローチを使用します。それはダックタイピングを使用し、次のような便利なことを行うことができます。template<T> T add(T v1, T v2) { return v1->add(v2); }そこにaddは、それらが何であっても2つのことをする関数を作成する本当に一般的な方法があり、add()1つのパラメータで名前が付けられたメソッドが必要です。
トリニダード

7
@Jobそれは確かに生成されたコードです。テンプレートは、Cプリプロセッサを置き換えることを目的としているため、非常に巧妙なトリックを作成できるはずであり、それ自体がチューリング完全言語です。Javaジェネリックはタイプセーフコンテナの砂糖に過ぎず、C#ジェネリックは優れていますが、それでも貧乏人のC ++テンプレートです。
トリニダード

3
@Job AFAIK、Javaジェネリックは新しいジェネリック型ごとにクラスを生成せず、ジェネリックメソッドを使用するコードに型キャストを追加するだけなので、実際にはIMOのメタプログラミングではありません。C#テンプレートは、ジェネリック型ごとに新しいコードを生成します。つまり、C#の世界でList <int>とList <double>を使用すると、それぞれのコードが生成されます。テンプレートと比較して、C#ジェネリックは、どのタイプをフィードできるかを知る必要があります。Add私が与えた簡単な例を実装することはできません。
トリニダード

26

すでに提供されている回答は、Java言語、JVM、およびJavaクラスライブラリの組み合わせに集中していることに注意してください。

Javaの言語に関する限り、Javaのジェネリックに問題はありません。C#vs Javaジェネリックで説明されているように、Javaのジェネリックは言語レベル1では非常に優れています。

準最適なのは、JVMがジェネリックを直接サポートしていないことです。これにはいくつかの大きな影響があります。

  • クラスライブラリのリフレクション関連部分は、Java言語で利用可能な完全な型情報を公開できません。
  • ある程度のパフォーマンスペナルティが伴います

Java言語は、ターゲットとしてJVMを使用し、コアクラスライブラリとしてJavaクラスライブラリを使用して遍在的にコンパイルされているため、私が行っている区別は、つまらないものだと思います。

1ワイルドカードを除いて、一般的なケースでは型推論が決定不能になると考えられています。これは、C#とJavaジェネリックの大きな違いであり、あまり言及されていません。ありがとう、アンチモン。


14
JVMはジェネリックをサポートする必要はありません。これは、ix86の実際のマシンがテンプレートをサポートしていないため、C ++にはテンプレートがないと言っているのと同じですが、これは誤りです。問題は、コンパイラがジェネリック型を実装するためのアプローチです。繰り返しになりますが、C ++テンプレートには実行時のペナルティはありません。リフレクションはまったく行われません。Javaで行う必要がある唯一の理由は、言語設計が不十分だからです。
トリニダード

2
@Trinidadですが、Javaにはジェネリックがないとは言わなかったので、Javaがどのように同じであるかわかりません。そして、ええ、JVMはありません必要な C ++がいないと同じように、それらをサポートするために必要なコードを最適化します。しかし、最適化しないことは、確かに「何か間違っている」と見なされます。
ローマンスターコフ

3
私が主張したのは、リフレクションを使用できるようにするために、JVMがジェネリックを直接サポートする必要がないということです。他の人がそれをやったので、それができるようになりました。問題は、Javaがジェネリックから新しいクラスを生成せず、具体化されないことです。
トリニダード

4
@Trinidad:C ++では、コンパイラに十分な時間とメモリが与えられていると仮定すると、テンプレートを使用して、コンパイル時に利用可能な情報で実行できるあらゆることを実行できます(テンプレートのコンパイルはチューリング完全であるため)が、プログラムの実行前には利用できない情報を使用してテンプレートを作成する方法はありません。.netでは、対照的に、プログラムは入力に基づいて型を作成できます。作成できるさまざまなタイプの数が宇宙の電子数を超えるプログラムを作成するのはかなり簡単です。
supercat

2
@Trinidad:明らかに、プログラムを1回実行するだけでこれらのすべてのタイプ(または実際、それらの無限小部分を超えるもの)を作成することはできませんでしたが、ポイントはそうする必要がないということです。実際に使用される型のみを作成する必要があります。任意の番号(例:)を受け入れ、それから8675309その番号に一意の型(例:)を作成できるプログラムを作成できます。このプログラムZ8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>は他の型とは異なるメンバーを持ちます。C ++では、任意の入力から生成できるすべての型は、コンパイル時に生成する必要があります。
supercat

13

通常の批判は、具体化の欠如です。つまり、実行時のオブジェクトにはジェネリック引数に関する情報が含まれていません(ただし、情報はフィールド、メソッド、コンストラクター、拡張クラスおよびインターフェイスに存在します)。あなたは、言って、キャストができることをこれが意味ArrayList<String>しますList<File>。コンパイラーは警告を出しますがArrayList<String>Object参照に割り当ててからにキャストした場合にも警告を出しますList<String>。利点は、ジェネリックを使用すると、おそらくキャストを行うべきではないため、不要なデータがなくてもパフォーマンスが向上し、もちろん下位互換性があることです。

一部の人々は、一般的な引数(void fn(Set<String>)およびvoid fn(Set<File>))に基づいてオーバーロードできないと不平を言っています。代わりに、より良いメソッド名を使用する必要があります。オーバーロードは静的なコンパイル時の問題であるため、このオーバーロードは具体化を必要としないことに注意してください。

プリミティブ型はジェネリックでは機能しません。

ワイルドカードと境界は非常に複雑です。彼らは非常に便利です。Javaが不変性とtell-don't-askインターフェースを好む場合、使用側ジェネリックよりも宣言側ジェネリックの方が適切です。


「オーバーロードは静的なコンパイル時の問題であるため、このオーバーロードは具体化を必要としないことに注意してください。」しませんか?具体化せずにどのように機能しますか?JVMは、どのメソッドを呼び出すかを知るために、実行時にどのタイプのセットを持っているかを知る必要はありませんか?
MatrixFrog

2
@MatrixFrogいいえ。私が言うように、オーバーロードはコンパイル時の問題です。コンパイラは、式の静的型を使用して特定のオーバーロードを選択します。これは、クラスファイルに書き込まれるメソッドです。あなたが持っているObject cs = new char[] { 'H', 'i' }; System.out.println(cs);場合は、ナンセンスが印刷されます。のタイプを変更csするchar[]と、が得られHiます。
トムホーティン-タックライン

10

Javaジェネリックは、次のことができないためにひどいです。

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

ジェネリックが実際にジェネリッククラスパラメーターである場合に、上記のコードのすべてのクラスを処理して動作させる必要はありません。


すべてのクラスを屠殺する必要はありません。適切なファクトリクラスを追加し、それをAsyncAdapterに注入するだけです。(基本的にこれはジェネリックに関するものではなく、コンストラクターがJavaで継承されないという事実です)。
ピーターテイラー

13
@PeterTaylor:適切なファクトリクラスは、すべてのボイラープレートを他の見えない混乱に押し込みますが、それでも問題は解決しません。新しいインスタンスをインスタンス化するたびにParser、ファクトリコードをさらに複雑にする必要があります。一方、「ジェネリック」が本当にジェネリックを意味するのであれば、工場やデザインパターンの名のとおりのその他のナンセンスは必要ありません。これは、動的言語で作業することを好む多くの理由の1つです。
-davidk01

2
テンプレートと仮想を使用する場合、コンストラクターのアドレスを渡すことができないC ++でも同様のことがあります。代わりにファクトリーファンクターを使用できます。
マークKコーワン

Javaは、メソッドの名前の前に「保護された」と書くことができないのでダメですか かわいそう。動的言語にこだわる。そして、特にHaskellに注意してください。
fdreger

5
  • 目的を果たさない一般的なパラメータが欠落していることに対するコンパイラの警告は、言語を無意味に冗長にします。 public String getName(Class<?> klazz){ return klazz.getName();}

  • ジェネリックは配列ではうまく機能しません

  • 型情報が失われると、反射がキャスティングとダクトテープの混乱になります。


HashMap代わりに使用するための警告が表示されたときにイライラしHashMap<String, String>ます。
マイケルK

1
@Michaelしかし、実際にジェネリックと一緒に使用する必要があります...-
代替

配列の問題は再現性につながります。説明については、Java Genericsブック(Alligator)を参照してください。
ncmathsadist

5

他の答えはこれをある程度言ったと思うが、あまりはっきりしていない。ジェネリックの問題の1つは、リフレクションプロセス中にジェネリック型が失われることです。たとえば、次のとおりです。

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

残念ながら、返された型は、arrのジェネリック型がStringであることを伝えることができません。それは微妙な違いですが、それは重要です。arrは実行時に作成されるため、ジェネリック型は実行時に消去されるため、それを把握することはできません。一部の人が述べたように、反射の観点からArrayList<Integer>と同じように見えArrayList<String>ます。

これはGenericsのユーザーにとっては重要ではないかもしれませんが、リフレクションを使用して、ユーザーがインスタンスの具体的なジェネリック型を宣言した方法についての凝った事柄を理解するためのいくつかの凝ったフレームワークを作成したいとしましょう。

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

インスタンスを作成するジェネリックファクトリが必要だったMySpecialObjectとします。これは、このインスタンスに対して宣言した具象ジェネリック型だからです。Well Factoryクラスは、Javaによって消去されたため、このインスタンスに対して宣言された具象型を見つけるために自身を問い合わせることができません。

.Netのジェネリックでは、実行時にオブジェクトがジェネリック型であることをコンパイラがバイナリにコンパイルしたためにオブジェクトが認識するため、これを行うことができます。消去では、Javaはこれを行うことができません。


1

ジェネリック医薬品について良いことをいくつか言うことができましたが、それは問題ではありませんでした。実行時に利用できず、配列で動作しないと文句を言うことができますが、それは言及されています。

大きな心理的迷惑: 私は時折、ジェネリックを機能させることができない状況に陥ります。(配列は最も単純な例です。)そして、ジェネリックが仕事をすることができないのか、私がただ愚かであるのかを理解することはできません。 嫌いです。 ジェネリックのようなものは常に機能するはずです。Java言語を使ってやりたいことができないときはいつでも、問題は私であり、プッシュし続ければ最終的にそこに着くことがわかります。ジェネリック医薬品を使用すると、しつこくなりすぎると、多くの時間を無駄にすることになります。

しかし、実際の問題は、ジェネリックがあまりにも多くの複雑さを追加しすぎて、利益が少なすぎることです。最も単純なケースでは、車を含むリストにリンゴを追加するのを止めることができます。いいよ しかし、ジェネリックがないと、このエラーは実行時にClassCastExceptionを非常に速くスローし、無駄な時間はほとんどありません。チャイルドシートにチャイルドシートのある車を追加する場合、リストはベビーチンパンジーを含むチャイルドシートの車のみを対象としているというコンパイル時の警告が必要ですか?プレーンオブジェクトインスタンスのリストは、良いアイデアのように見え始めます。

Generic'edコードは多くの単語と文字を持ち、多くの余分なスペースを取り、読むのにより多くの時間がかかります。余分なコードをすべて正しく動作させるのに多くの時間を費やすことができます。それが起こるとき、私はめちゃくちゃ賢く感じます。また、私は自分の時間の数時間以上を無駄にし、他の誰かがコードを理解できるようになるかどうか疑問に思う必要があります。自分よりも賢くない人や無駄な時間が少ない人に、メンテナンスのために引き渡したいと思います。

その一方で(私はそこに少し傷ついて、バランスをとる必要があると感じています)、単純なコレクションとマップを使用して、書き込み、書き込み、追加、チェック、チェックを行い、通常はあまり追加しませんコードの複雑さあれば 誰か 他の コレクションやマップを書き込みます)。そして、JavaはC#よりも優れています。私が使用したいC#コレクションはどれもジェネリックを扱うことはないようです。(コレクションに奇妙な趣味があることは認めます。)


すてきな暴言。しかし、Guice、Gson、Hibernateなど、ジェネリックなしで存在する可能性のある多くのライブラリ/フレームワークがあります。そして、ジェネリックは慣れればそれほど難しくありません。型引数の入力と読み取りは実際のPITAです。ここでvalは、使用できる場合に役立ちます。
maaartinus

1
@maaartinus:少し前に書きました。また、OPは「問題」を要求しました。ジェネリックは非常に便利で、とても気に入っています。今よりもずっと多くなっています。ただし、独自のコレクションを作成している場合、学習するのは非常に困難です。コレクションのタイプが実行時に決定されると、コレクションのエントリのクラスを通知するメソッドがコレクションにあると、その有用性は薄れます。この時点でジェネリックは機能せず、IDEは何百もの意味のない警告を生成します。Javaプログラミングの99.99%はこれに関係していません。しかし、私は思いますちょうど持っていた多くの私は上記を書いたとき、これにトラブルを。
RalphChapin

JavaとC#の両方の開発者である私は、なぜJavaコレクションを好むのでしょうか?
イヴァイロスラヴォフ14年

Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.これは、プログラムの起動と問題のコード行のヒットとの間にどれだけの実行時間が発生するかに本当に依存します。ユーザーがエラーを報告し、それを再現することができ、数分(または、最悪のシナリオ、時間または数日)を取る場合、そのコンパイル時の検証は...良く見て開始します
メイソンウィーラー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.