回答:
私はノイズに声を加えて、物事を明確にすることを試みます:
List<Person> foo = new List<Person>();
そして、コンパイラーはPerson
リストにないものを入れないようにします。
背後ではC#コンパイラがList<Person>
.NET dllファイルに入れているだけですが、実行時にJITコンパイラが新しいコードセットを作成してビルドします。ListOfPerson
。です。
これの利点は、それが本当に速くなることです。キャストやその他の要素はありません。dllには、これがのリストであるという情報が含まれているため、Person
後でリフレクションを使用して調べた他のコードは、Person
オブジェクトが含まれていることを通知できます(インテリセンスなどを取得します)。
これの欠点は、古いC#1.0および1.1コード(ジェネリックを追加する前)がこれらの新しいを理解しないためList<something>
、手動で変換してプレーンな古いコードに戻す必要があることです。List
して相互運用する必要があることです。C#2.0バイナリコードには下位互換性がないため、これはそれほど大きな問題ではありません。これが発生するのは、古いC#1.0 / 1.1コードをC#2.0にアップグレードする場合のみです。
ArrayList<Person> foo = new ArrayList<Person>();
表面的には同じように見えますが、まあまあです。コンパイラーはPerson
、リストにないものを入れないようにします。
違いは、舞台裏で何が起こるかです。C#とは異なり、Javaは特別なビルドを行いませんListOfPerson
- ArrayList
常にJavaで使用されているプレーンな古いものを使用するだけです。配列から何かを取り出しても、通常のPerson p = (Person)foo.get(1);
キャストダンスを実行する必要があります。コンパイラーはキー入力を節約しますが、いつものように速度のヒット/キャスティングはまだ発生します。
人々が「Type Erasure」に言及するとき、これは彼らが話していることです。コンパイラーがキャストを挿入してから、それがPerson
単なるリストではないことを意味するという事実を「消去」しますObject
このアプローチの利点は、ジェネリックスを理解しない古いコードが気にする必要がないことです。それArrayList
はいつもと同じ古いものをまだ扱っています。ジェネリックでJava 5を使用してコードをコンパイルし、それを古い1.4または以前のJVMで実行することをサポートしたかったため、これはJavaの世界でより重要です。
欠点は、前述のスピードヒットです。またListOfPerson
、.classファイルに入る疑似クラスなどがないため、後でそれを調べるコード(リフレクションを使用するか、別のコレクションからプルした場合)変換された場所Object
など)はPerson
、他の配列リストだけではなく、それだけを含むリストであることを意味しているとはまったく言えません。
std::list<Person>* foo = new std::list<Person>();
これはC#とJavaのジェネリックのように見え、期待どおりに動作しますが、裏ではさまざまなことが起こっています。
pseudo-classes
Javaのように型情報を捨てるのではなく、特別に構築するという点でC#ジェネリックと最も共通していますが、まったく別の魚のやかんです。
C#とJavaはどちらも、仮想マシン用に設計された出力を生成します。Person
クラスが含まれているコードを記述する場合、どちらの場合も、Person
クラスに関するいくつかの情報が.dllまたは.classファイルに入り、JVM / CLRはこれを実行します。
C ++は未加工のx86バイナリコードを生成します。すべてがオブジェクトではなく、Person
クラスについて知る必要のある基礎となる仮想マシンはありません。ボクシングやアンボクシングはありません。また、関数はクラスに属している必要はありません。
このため、C ++コンパイラーはテンプレートを使用して実行できることを制限しません。基本的に、手動で作成できるコードであれば、テンプレートを使用して作成できます。
最も明白な例は、物事を追加することです:
C#とJavaでは、ジェネリックシステムはクラスで使用できるメソッドを認識している必要があり、これを仮想マシンに渡す必要があります。これを伝える唯一の方法は、実際のクラスをハードコーディングするか、インターフェイスを使用することです。例えば:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
型T
がName()というメソッドを実際に提供していることがわからないため、そのコードはC#またはJavaでコンパイルされません。あなたはそれを言わなければなりません-このようなC#で:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
次に、addNamesに渡すものにIHasNameインターフェースなどを実装する必要があります。Java構文は異なりますが(<T extends IHasName>
)、同じ問題が発生します。
この問題の「古典的な」ケースは、これを行う関数を記述しようとすることです
string addNames<T>( T first, T second ) { return first + second; }
+
メソッドを含むインターフェースを宣言する方法がないため、実際にこのコードを書くことはできません。あなたは失敗します。
C ++はこれらの問題のどれにも悩まされていません。コンパイラーは、タイプをVMに渡すことを考慮しません。両方のオブジェクトに.Name()関数がある場合、コンパイルされます。そうでない場合は、そうなりません。シンプル。
だから、それがあります:-)
int addNames<T>( T first, T second ) { return first + second; }
C#では記述できないという文には反対です。ジェネリック型は、インターフェイスではなくクラスに制限することができ、+
演算子を含むクラスを宣言する方法があります。
C ++が「ジェネリック」の用語を使用することはほとんどありません。代わりに、「テンプレート」という単語が使用され、より正確になります。テンプレートは、一般的な設計を実現するための1つの手法を説明します。
C ++テンプレートは、C#とJavaの両方が実装するものとは大きく異なります。最初の理由は、C ++テンプレートはコンパイル時の型引数だけでなく、コンパイル時のconst-value引数も許可することです。テンプレートは整数または関数シグネチャとしても指定できます。これは、たとえば計算など、コンパイル時にかなりファンキーなことを実行できることを意味します。
template <unsigned int N>
struct product {
static unsigned int const VALUE = N * product<N - 1>::VALUE;
};
template <>
struct product<1> {
static unsigned int const VALUE = 1;
};
// Usage:
unsigned int const p5 = product<5>::VALUE;
このコードは、C ++テンプレートの他の際立った機能、つまりテンプレートの特殊化も使用します。コードは、1 product
つの値の引数を持つ1 つのクラステンプレートを定義します。また、引数が1と評価されるたびに使用される、そのテンプレートの特殊化も定義します。これにより、テンプレート定義の再帰を定義できます。これはAndrei Alexandrescuによって最初に発見されたと思います。
テンプレートの特殊化は、データ構造の構造的な違いを可能にするため、C ++にとって重要です。テンプレートは全体として、タイプ間でインターフェースを統合する手段です。ただし、これは望ましいことですが、すべての型を実装内で等しく扱うことはできません。C ++テンプレートはこれを考慮に入れます。これは、仮想メソッドのオーバーライドにより、インターフェイスと実装の間でOOPが行う違いとほとんど同じです。
C ++テンプレートは、そのアルゴリズムプログラミングパラダイムに不可欠です。たとえば、コンテナのほとんどすべてのアルゴリズムは、コンテナタイプをテンプレートタイプとして受け入れ、それらを均一に扱う関数として定義されています。実際、それは正しくありません。C++はコンテナーでは機能せず、コンテナーの最初と最後を指す2つのイテレーターで定義された範囲で機能します。したがって、コンテンツ全体は反復子によって制限されます。開始<=要素<終了。
コンテナの代わりにイテレータを使用すると、コンテナ全体ではなくコンテナの一部を操作できるため便利です。
C ++のもう1つの際立った機能は、クラステンプレートの部分的な特殊化の可能性です。これは、Haskellや他の関数型言語での引数のパターンマッチングに多少関係しています。たとえば、要素を格納するクラスを考えてみましょう:
template <typename T>
class Store { … }; // (1)
これはどの要素タイプでも機能します。しかし、特別なトリックを適用することで、他のタイプよりも効率的にポインターを格納できるとしましょう。これを行うには、すべてのポインター型を部分的に特殊化します。
template <typename T>
class Store<T*> { … }; // (2)
ここで、1つのタイプのコンテナテンプレートをインスタンス化するときは常に、適切な定義が使用されます。
Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
Anders Hejlsberg氏自身、ここでの違いについて「C#、Java、C ++のジェネリック」で説明しています。
良い答えの多くは、上ですでに存在するどのようなので、私は少し異なる視点を与え、追加してみましょう、違いがある理由を。
すでに説明したように、主な違いは型の消去です。つまり、Javaコンパイラーがジェネリック型を消去し、それらが生成されたバイトコードに含まれないという事実です。しかし、問題は、なぜ誰もがそうするのでしょうか?意味がありません!それとも?
さて、代替手段は何ですか?あなたが言語でジェネリックを実装していない場合は、どこかあなたはそれらを実装しますか?そして答えは、仮想マシン内です。これは後方互換性を壊します。
一方、型消去では、汎用クライアントと非汎用ライブラリを混在させることができます。つまり、Java 5でコンパイルされたコードは、Java 1.4にもデプロイできます。
ただし、マイクロソフトはジェネリックの下位互換性を解除することを決定しました。これが、.NET GenericsがJava Genericsより「優れている」理由です。
もちろん、Sunはばかや臆病者ではありません。彼らが「うまくいかなくなった」理由は、Javaがジェネリックを導入したとき、.NETよりもかなり古く、広く普及していたためです。(これらは両方の世界でほぼ同時に導入されました。)下位互換性を壊すことは非常に困難でした。
さらに言い換えると、Javaではジェネリックは言語の一部であり(つまり、Javaにのみ適用され、他の言語には適用されません)、. NETでは、仮想マシンの一部です(つまり、すべての言語に適用され、 C#とVisual Basic.NETのみ)。
これを、LINQ、ラムダ式、ローカル変数型推論、匿名型、式ツリーなどの.NET機能と比較してください。これらはすべて言語機能です。そのため、VB.NETとC#の間には微妙な違いがあります。それらの機能がVMの一部である場合、すべての言語で同じになります。しかし、CLRは変更されていません。.NET3.5 SP1でも、.NET 2.0と同じです。.NET 3.5ライブラリを使用しない場合は、LINQを使用するC#プログラムを.NET 3.5コンパイラでコンパイルし、.NET 2.0で実行できます。それは考えていないジェネリック医薬品と.NET 1.1で動作しますが、それは考えたJavaおよびJava 1.4で動作します。
ArrayList<T>
、(非表示の)静的Class<T>
フィールドを持つ、内部的に名前が付けられた新しいタイプとして出力できます。ジェネリックlibの新しいバージョンが1.5バイト以上のコードでデプロイされている限り、1.4のJVMで実行できます。
以前の投稿のフォローアップ。
テンプレートは、使用されているIDEに関係なく、C ++がIntelliSenseで非常に失敗する主な理由の1つです。テンプレートは特殊化されているため、特定のメンバーが存在するかどうかをIDEが確認することはできません。考慮してください:
template <typename T>
struct X {
void foo() { }
};
template <>
struct X<int> { };
typedef int my_int_type;
X<my_int_type> a;
a.|
現在、カーソルは指定された位置にあり、メンバーa
が持っているかどうか、何を持っているかをIDEがその時点で言うのは難しいです。他の言語の場合、解析は簡単ですが、C ++の場合、事前にかなりの評価が必要です。
悪くなる。my_int_type
クラステンプレート内でも定義されている場合はどうなりますか?現在、その型は別の型引数に依存しています。そして、ここでもコンパイラーは失敗します。
template <typename T>
struct Y {
typedef T my_type;
};
X<Y<int>::my_type> b;
:思考の少し後に、プログラマはこのコードは上記と同じであると結論うY<int>::my_type
に解決さint
ゆえ、b
同じタイプである必要がありa
、右、?
違う。コンパイラーがこのステートメントを解決しようとした時点では、Y<int>::my_type
まだ実際にはわかりません!したがって、これが型であることはわかりません。メンバー関数やフィールドなど、他の何かである可能性があります。これにより、あいまいさが生じる可能性があります(現在のケースではありません)。そのため、コンパイラーは失敗します。型名を参照することを明示的に伝える必要があります。
X<typename Y<int>::my_type> b;
これで、コードがコンパイルされます。この状況からあいまいさがどのように発生するかを確認するには、次のコードを検討してください。
Y<int>::my_type(123);
このコードステートメントは完全に有効であり、C ++にへの関数呼び出しを実行するように指示しY<int>::my_type
ます。ただし、my_type
が関数ではなくタイプの場合でも、このステートメントは有効であり、しばしばコンストラクター呼び出しである特別なキャスト(関数スタイルのキャスト)を実行します。コンパイラーはどちらを意味するのか判断できないため、ここで曖昧さをなくす必要があります。
JavaとC#はどちらも、最初の言語リリース後にジェネリックを導入しました。ただし、ジェネリックが導入されたときにコアライブラリがどのように変更されたかには違いがあります。 C#のジェネリックは単なるコンパイラーマジックではないため、下位互換性を損なうことなく既存のライブラリクラスを生成することはできませんでした。
たとえば、Javaでは、既存のコレクションフレームワークが完全に汎用化されました。 Javaには、コレクションクラスのジェネリックバージョンとレガシー非ジェネリックバージョンの両方はありません。 いくつかの点で、これははるかにクリーンです。C#でコレクションを使用する必要がある場合、非ジェネリックバージョンを使用する理由はほとんどありませんが、これらのレガシークラスはそのまま残り、状況が乱雑になります。
もう1つの注目すべき違いは、JavaとC#のEnumクラスです。 JavaのEnumには、このやや曲がりくねった定義があります。
// java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
(これがそうである理由の正確な説明については、 Angelika Langerの非常に明確な説明を参照してください。本質的に、これはJavaが文字列からそのEnum値への型保証アクセスを提供できることを意味します:
// Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");
これをC#のバージョンと比較します。
// Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");
Enumは、ジェネリックが言語に導入される前にC#に既に存在していたため、既存のコードを壊さずに定義を変更することはできませんでした。したがって、コレクションと同様に、このレガシー状態でもコアライブラリに残ります。
ArrayList
を変更List<T>
して新しい名前空間に配置する必要がある理由はありません。実際には、クラスがソースコードに表示さArrayList<T>
れた場合、それがILコードで別のコンパイラによって生成されたクラス名になるため、名前の競合は発生しません。
11か月遅れますが、この質問はいくつかのJavaワイルドカード関連のものに対応できると思います。
これはJavaの構文機能です。次のメソッドがあるとします。
public <T> void Foo(Collection<T> thing)
また、メソッド本体で型Tを参照する必要がないとします。名前Tを宣言し、それを1回だけ使用しているのに、なぜその名前を考える必要があるのでしょうか。代わりに、次のように書くことができます:
public void Foo(Collection<?> thing)
疑問符は、コンパイラーに、その場所に1回だけ出現する必要がある通常の名前付き型パラメーターを宣言したふりをするように要求します。
ワイルドカードを使用してできることは、名前付きの型パラメーターを使用して実行することはできません(これは、C ++およびC#でこれらが常に行われる方法です)。
class Foo<T extends List<?>>
そして使用しますFoo<StringList>
が、C#ではあなたはその余分な型パラメーターを追加する必要があります:class Foo<T, T2> where T : IList<T2>
とclunkyを使用しFoo<StringList, String>
ます。
ウィキペディアには、Java / C#ジェネリックとJavaジェネリック/ C ++テンプレートの両方を比較した優れた記事があります。Genericsのメインの記事は少し雑然としているように見えますが、その中にいくつかの良い情報があります。
最大の不満は型消去です。その中で、ジェネリックは実行時に適用されません。 この件に関するSunのドキュメントへのリンクは次のとおりです。
ジェネリックは型消去によって実装されます。ジェネリック型情報はコンパイル時にのみ存在し、その後コンパイラーによって消去されます。
他の非常に興味深い提案の中に、ジェネリックの洗練と下位互換性の破壊に関するものがあるように見えます:
現在、ジェネリックは消去を使用して実装されています。つまり、ジェネリック型情報は実行時に利用できないため、ある種のコードを書くのが難しくなっています。ジェネリックはこの方法で実装され、古い非ジェネリックコードとの下位互換性をサポートします。ジェネリファイを具体化すると、実行時にジェネリック型情報が利用可能になり、レガシー非ジェネリックコードが破損します。ただし、Neal Gafterは、下位互換性を壊さないように、指定された場合にのみ型をreifiableにすることを提案しています。
注意:コメントするのに十分なポイントがないので、コメントとして適切な回答に移動してください。
ポピュラーな信念とは対照的に、それがどこから来たのか私には理解できませんでしたが、.netは後方互換性を損なうことなく真のジェネリックを実装しました。.net 2.0で使用するためだけに、ジェネリックでない.net 1.0コードをジェネリックに変更する必要はありません。ジェネリックリストと非ジェネリックリストの両方は、4.0まででも.Net Framework 2.0で引き続き使用できます。これは、下位互換性の理由だけです。したがって、ジェネリック以外のArrayListを使用していた古いコードは引き続き機能し、以前と同じArrayListクラスを使用します。下位コードの互換性は、1.0から現在まで常に維持されています...したがって、.net 4.0でも、そうする場合は、1.0 BCLの非ジェネリッククラスを使用するオプションを選択する必要があります。
そのため、真のジェネリックをサポートするためにjavaが下位互換性を解除する必要はないと思います。
ArrayList<Foo>
することになっている古いメソッドに渡したいがあると仮定ArrayList
しますFoo
。ArrayList<foo>
がでない場合、それArrayList
をどのように機能させますか?