Java にはジェネリックがあり、C++ はtemplate
s を使用して非常に強力なプログラミング モデルを提供します。では、C++ と Java ジェネリックの違いは何ですか?
13 に答える
それらの間には大きな違いがあります。C++ では、ジェネリック型のクラスまたはインターフェイスを指定する必要はありません。そのため、型付けが緩くなるという注意点を除いて、真に汎用的な関数とクラスを作成できます。
template <typename T> T sum(T a, T b) { return a + b; }
上記のメソッドは、同じ型の 2 つのオブジェクトを追加し、"+" 演算子を使用できる任意の型 T に使用できます。
Java では、渡されたオブジェクトのメソッドを呼び出す場合は、次のように型を指定する必要があります。
<T extends Something> T sum(T a, T b) { return a.add ( b ); }
C++ では、コンパイラが (呼び出される) さまざまな型に対してさまざまな関数を生成するため、汎用関数/クラスはヘッダーでのみ定義できます。したがって、コンパイルは遅くなります。Javaでは、コンパイルに大きなペナルティはありませんが、Javaは実行時にジェネリック型が消去される「消去」と呼ばれる手法を使用するため、実行時にJavaは実際に...
Something sum(Something a, Something b) { return a.add ( b ); }
したがって、Java でのジェネリック プログラミングはあまり役に立ちません。新しい foreach 構造を支援するためのほんの少しの構文糖衣にすぎません。
編集:有用性に関する上記の意見は、若い自己によって書かれました。もちろん、Java のジェネリックは型安全性に役立ちます。
Java Genericsは、C++テンプレートとは大きく異なります。
基本的にC++では、テンプレートは基本的に栄光のプリプロセッサ/マクロセットです(注:一部の人々は類推を理解できないように見えるので、テンプレート処理がマクロであるとは言いません)。Javaでは、これらは基本的に、オブジェクトの定型的なキャストを最小限に抑えるための構文糖衣です。これは、 C++テンプレートとJavaジェネリックのかなりまともな紹介です。
この点について詳しく説明すると、C ++テンプレートを使用する場合、基本的には、#define
マクロを使用する場合と同じように、コードの別のコピーを作成することになります。これによりint
、配列のサイズなどを決定するパラメータをテンプレート定義に含めるなどのことができます。
Javaはそのようには機能しません。Javaでは、すべてのオブジェクトがjava.lang.Objectから拡張されるため、ジェネリック以前では、次のようなコードを記述します。
public class PhoneNumbers {
private Map phoneNumbers = new HashMap();
public String getPhoneNumber(String name) {
return (String) phoneNumbers.get(name);
}
}
すべてのJavaコレクションタイプがオブジェクトをベースタイプとして使用しているため、何でも入れることができます。Java 5はロールアラウンドしてジェネリックスを追加するため、次のようなことができます。
public class PhoneNumbers {
private Map<String, String> phoneNumbers = new HashMap<String, String>();
public String getPhoneNumber(String name) {
return phoneNumbers.get(name);
}
}
そして、これがすべてのJavaジェネリックです。オブジェクトをキャストするためのラッパーです。これは、JavaGenericsが洗練されていないためです。型消去を使用します。この決定が下されたのは、Java Genericsが非常に遅い時期に登場したため、下位互換性を壊したくなかったためです(aMap<String, String>
が必要な場合はいつでも使用できMap
ます)。これを、型消去が使用されていない.Net / C#と比較してください。これにより、あらゆる種類の違いが生じます(たとえば、プリミティブ型を使用できIEnumerable
、IEnumerable<T>
相互に関係はありません)。
また、Java 5以降のコンパイラでコンパイルされたジェネリックスを使用するクラスは、JDK 1.4で使用できます(Java 5以降を必要とする他の機能やクラスを使用しないことを前提としています)。
そのため、JavaGenericsはシンタックスシュガーと呼ばれています。
しかし、ジェネリックスの実行方法に関するこの決定は非常に大きな影響を与えるため、(優れた)JavaジェネリックスFAQは、Javaジェネリックスについて人々が抱く多くの質問に答えるために生まれました。
C ++テンプレートには、JavaGenericsにはない多くの機能があります。
プリミティブ型の引数の使用。
例えば:
template<class T, int i> class Matrix { int T[i][i]; ... }
Javaでは、ジェネリックスでプリミティブ型の引数を使用することはできません。
デフォルトの型引数の使用。これはJavaで見逃している機能の1つですが、これには下位互換性の理由があります。
Javaでは引数の境界を設定できます。
例えば:
public class ObservableList<T extends List> { ... }
異なる引数を使用したテンプレート呼び出しは実際には異なるタイプであることを強調する必要があります。静的メンバーも共有しません。Javaではこれは当てはまりません。
ジェネリックスとの違いは別として、完全を期すために、C ++とJava(および別の1つ)の基本的な比較を示します。
また、Javaで考えることを提案することもできます。C ++プログラマーとして、オブジェクトのような概念の多くはすでに第二の性質ですが、微妙な違いがあるため、パーツをざっと読んだとしても、紹介テキストを用意する価値があります。
Javaを学習するときに学ぶことの多くは、すべてのライブラリです(標準(JDKに含まれるもの)と非標準(Springなどの一般的に使用されるものを含む)の両方)。Java構文はC++構文よりも冗長であり、多くのC ++機能(演算子のオーバーロード、多重継承、デストラクタメカニズムなど)はありませんが、厳密にはC++のサブセットにはなりません。
C++ にはテンプレートがあります。Java にはジェネリックがあります。これは C++ テンプレートのように見えますが、非常に異なっています。
テンプレートは、その名前が示すように、テンプレート パラメーターを入力することでタイプ セーフなコードを生成するために使用できるテンプレートをコンパイラーに提供することによって機能します。
私が理解しているように、ジェネリックは逆に機能します。型パラメーターは、それらを使用するコードが型安全であることを確認するためにコンパイラーによって使用されますが、結果のコードは型なしで生成されます。
C++ テンプレートは非常に優れたマクロ システムであり、Java ジェネリックは型キャストを自動的に生成するためのツールであると考えてください。
C ++テンプレートには、Javaジェネリックにはないもう1つの機能があり、特殊化があります。これにより、特定のタイプに対して異なる実装を行うことができます。したがって、たとえば、intの高度に最適化されたバージョンを使用しながら、残りのタイプの汎用バージョンを使用することができます。または、ポインタタイプと非ポインタタイプで異なるバージョンを使用できます。これは、ポインタを渡されたときに逆参照されたオブジェクトを操作する場合に便利です。
このトピックについては、 モーリス・ナフタリン、フィリップ・ワドラーによるJava GenericsandCollectionsにすばらしい説明があります。この本を強くお勧めします。引用するには:
Javaのジェネリックは、C++のテンプレートに似ています。...構文は意図的に類似しており、セマンティクスは意図的に異なります。...意味的には、Javaジェネリックは消去によって定義されますが、C++テンプレートは拡張によって定義されます。
ここで完全な説明を読んでください。
(出典:oreilly.com)
Java(およびC#)ジェネリックは、単純な実行時型置換メカニズムのようです。
C ++テンプレートは、ニーズに合わせて言語を変更する方法を提供するコンパイル時の構成です。これらは実際には、コンパイラがコンパイル中に実行する純粋な関数型言語です。
C++ テンプレートのもう 1 つの利点は、特殊化です。
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Special sum(const Special& a, const Special& b) { return a.plus(b); }
ここで、ポインターを指定して sum を呼び出すと、2 番目のメソッドが呼び出されます。非ポインター オブジェクトを指定して sum を呼び出すと、最初のメソッドが呼び出され、オブジェクトを指定して呼び出すsum
とSpecial
、3 番目のメソッドが呼び出されます。これはJavaでは不可能だと思います。
テンプレートは新しい型を作成し、ジェネリックは既存の型を制限します。
@キース:
template
そのコードは実際には間違っており、小さな不具合 (省略、特殊化構文の見た目が異なる)を除けば、部分的な特殊化は関数テンプレートでは機能せず、クラス テンプレートでのみ機能します。ただし、コードは、単純な古いオーバーロードを使用する代わりに、部分的なテンプレートの特殊化なしで機能します。
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
テンプレートはマクロシステムに他なりません。構文糖。それらは、実際のコンパイルの前に完全に展開されます (または、少なくとも、コンパイラはあたかもそうであるかのように動作します)。
例:
2 つの関数が必要だとしましょう。1 つの関数は、数値の 2 つのシーケンス (リスト、配列、ベクトルなど) を取り、それらの内積を返します。別の関数は長さを取り、その長さの 2 つのシーケンスを生成し、それらを最初の関数に渡し、その結果を返します。問題は、2 番目の関数で間違いを犯す可能性があることです。そのため、これら 2 つの関数は実際には同じ長さではありません。この場合、コンパイラが警告を出す必要があります。プログラムの実行中ではなく、コンパイル中です。
Java では、次のようなことができます。
import java.io.*;
interface ScalarProduct<A> {
public Integer scalarProduct(A second);
}
class Nil implements ScalarProduct<Nil>{
Nil(){}
public Integer scalarProduct(Nil second) {
return 0;
}
}
class Cons<A implements ScalarProduct<A>> implements ScalarProduct<Cons<A>>{
public Integer value;
public A tail;
Cons(Integer _value, A _tail) {
value = _value;
tail = _tail;
}
public Integer scalarProduct(Cons<A> second){
return value * second.value + tail.scalarProduct(second.tail);
}
}
class _Test{
public static Integer main(Integer n){
return _main(n, 0, new Nil(), new Nil());
}
public static <A implements ScalarProduct<A>>
Integer _main(Integer n, Integer i, A first, A second){
if (n == 0) {
return first.scalarProduct(second);
} else {
return _main(n-1, i+1,
new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
//the following line won't compile, it produces an error:
//return _main(n-1, i+1, first, new Cons<A>(i*i, second));
}
}
}
public class Test{
public static void main(String [] args){
System.out.print("Enter a number: ");
try {
BufferedReader is =
new BufferedReader(new InputStreamReader(System.in));
String line = is.readLine();
Integer val = Integer.parseInt(line);
System.out.println(_Test.main(val));
} catch (NumberFormatException ex) {
System.err.println("Not a valid number");
} catch (IOException e) {
System.err.println("Unexpected IO ERROR");
}
}
}
C# でもほぼ同じことが書けます。C++ で書き直してみてください。コンパイルできず、テンプレートが無限に拡張されます。