C++ と Java の「ジェネリック」型の違いは何ですか?
-
09-06-2019 - |
質問
Java にはジェネリックスがあり、C++ には非常に強力なプログラミング モデルが提供されます。 template
s.それでは、C++ と Java ジェネリックの違いは何でしょうか?
解決
それらの間には大きな違いがあります。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 ジェネリックは 大量に C++ テンプレートとは異なります。
基本的に C++ テンプレートは基本的に美化されたプリプロセッサ/マクロ セットです (注記: たとえを理解できない人もいるようなので、テンプレート処理がマクロだと言っているわけではありません)。Java では、これらは基本的にオブジェクトのボイラープレート キャストを最小限に抑えるための糖衣構文です。ここはかなりまともです C++ テンプレートと Java ジェネリックの概要.
この点について詳しく説明すると、次のようになります。C++ テンプレートを使用する場合、基本的には、あたかも C++ テンプレートを使用したかのように、コードの別のコピーを作成することになります。 #define
大きい。これにより、次のようなことが可能になります int
配列などのサイズを決定するテンプレート定義内のパラメーター。
Java はそのようには機能しません。Java では、すべてのオブジェクトの範囲は次のとおりです。 java.lang.オブジェクト したがって、ジェネリック以前は、次のようなコードを書くことになります。
public class PhoneNumbers {
private Map phoneNumbers = new HashMap();
public String getPhoneNumber(String name) {
return (String)phoneNumbers.get(name);
}
...
}
なぜなら、すべての Java コレクション型は基本型として Object を使用しており、その中に何でも入れることができるからです。Java 5 ではジェネリックが追加され、次のようなことができるようになります。
public class PhoneNumbers {
private Map<String, String> phoneNumbers = new HashMap<String, String>();
public String getPhoneNumber(String name) {
return phoneNumbers.get(name);
}
...
}
Java ジェネリックは次のとおりです。オブジェクトをキャストするためのラッパー。それは、Java ジェネリックが洗練されていないためです。彼らはタイプ消去を使用します。この決定は、Java ジェネリックスが登場したのがかなり後になってからであり、下位互換性を壊したくなかったために行われました ( Map<String, String>
いつでも使用可能 Map
が求められます)。これを、型消去が使用されていない .Net/C# と比較すると、あらゆる種類の違いが生じます (例:プリミティブ型を使用できます。 IEnumerable
そして IEnumerable<T>
相互に関係はありません)。
また、Java 5 以降のコンパイラでコンパイルされたジェネリックスを使用するクラスは、JDK 1.4 で使用できます (Java 5 以降を必要とする他の機能やクラスを使用していないと仮定します)。
それが Java ジェネリックと呼ばれる理由です 糖衣構文.
しかし、ジェネリック医薬品をどうするかについてのこの決定は、(素晴らしい) Java ジェネリックに関する FAQ は、Java ジェネリックに関して人々が抱いている非常に多くの質問に答えるために誕生しました。
C++ テンプレートには、Java ジェネリックにはない多くの機能があります。
プリミティブ型引数の使用。
例えば:
template<class T, int i> class Matrix { int T[i][i]; ... }
Java では、ジェネリックスでプリミティブ型の引数を使用することはできません。
の使用 デフォルトの型引数, これは Java に欠けている機能の 1 つですが、これには下位互換性の理由があります。
- Java では引数の境界を許可します。
例えば:
public class ObservableList<T extends List> {
...
}
異なる引数を使用したテンプレート呼び出しは実際には異なるタイプであることを強調する必要があります。静的メンバーさえ共有しません。Java ではこれは当てはまりません。
ジェネリックとの違いは別として、完全を期すために、次のとおりです。 C++ と Java の基本的な比較 (そして もう一つ).
そして私も提案できます Javaで考える. 。C++ プログラマにとって、オブジェクトなどの多くの概念はすでに自然なものですが、微妙な違いがあるため、部分的に流し読みする場合でも、入門テキストを用意する価値はあります。
Java を学習するときに学ぶことの多くは、すべてのライブラリです (標準 (JDK に含まれているもの) と非標準 (Spring などの一般的に使用されるものを含む) の両方) です。Java 構文は C++ 構文よりも冗長であり、C++ の機能はあまりありません (例:演算子のオーバーロード、多重継承、デストラクター メカニズムなど)、ただし厳密には C++ のサブセットになるわけでもありません。
C++にはテンプレートがあります。Java にはジェネリックがあり、C++ テンプレートに似ていますが、非常に異なります。
名前が示すように、テンプレートは、テンプレート パラメーターを入力することでタイプ セーフ コードを生成するために使用できる (ちょっと待ってください...) テンプレートをコンパイラーに提供することによって機能します。
私の理解では、ジェネリックは逆に機能します。型パラメーターは、それを使用するコードが型安全であることを検証するためにコンパイラーによって使用されますが、結果のコードは型をまったく使用せずに生成されます。
C++ テンプレートを次のように考えてください。 とてもいいです マクロ システム、およびタイプキャストを自動生成するツールとしての Java ジェネリックス。
C++ テンプレートにあって Java ジェネリックにはないもう 1 つの機能は特殊化です。これにより、特定の型に対して異なる実装を行うことができます。したがって、たとえば、高度に最適化されたバージョンを 整数, 、残りのタイプの汎用バージョンはまだあります。または、ポインター型と非ポインター型で異なるバージョンを使用することもできます。これは、ポインターを渡されたときに逆参照されたオブジェクトを操作する場合に便利です。
このトピックについては、素晴らしい説明があります。 Java ジェネリックとコレクションモーリス・ナフタリン、フィリップ・ワドラー著。この本を強くお勧めします。引用するには:
Javaのジェネリックは、C ++のテンプレートに似ています。...構文は意図的に似ており、セマンティクスは意図的に異なります。...意味的には、Javaジェネリックは消去によって定義されます。この場合、C ++テンプレートは拡張によって定義されます。
説明全文をお読みください ここ.
(ソース: oreilly.com)
基本的に、私の知る限り、C++ テンプレートは各型のコードのコピーを作成しますが、Java ジェネリックはまったく同じコードを使用します。
そう、あなた と言える C++ テンプレートは Java ジェネリックと同等です コンセプト (Java ジェネリックは概念上 C++ と同等であると言ったほうが適切ですが)
C++ のテンプレート メカニズムに精通している場合は、ジェネリックが似ていると思うかもしれませんが、その類似点は表面的なものです。ジェネリックスは、専門化ごとに新しいクラスを生成せず、「テンプレート メタプログラミング」も許可しません。
から: Java ジェネリックス
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); }
以下の答えは本に載っていたものです コーディングインタビューを解読する 第 13 章の解決策。非常に良いと思います。
Java ジェネリックの実装は、「型消去」という考えに基づいています。この技術は、ソース コードが Java 仮想マシン (JVM) バイトコードに変換されるときに、パラメーター化された型を削除します。たとえば、以下の Java コードがあるとします。
Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);
コンパイル中に、このコードは次のように書き直されます。
Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);
Java ジェネリックを使用しても、私たちの機能はあまり変わりませんでした。それは物事を少し美しくしただけです。このため、Java ジェネリックは「構文シュガー:」と呼ばれることもあります。
これは C++ とはまったく異なります。C++ では、テンプレートは本質的に美化されたマクロ セットであり、コンパイラは型ごとにテンプレート コードの新しいコピーを作成します。この証拠は、MyClass のインスタンスが MyClass と静的変数を共有しないという事実にあります。ただし、MyClass の 2 つのインスタンスは静的変数を共有します。
/*** MyClass.h ***/
template<class T> class MyClass {
public:
static int val;
MyClass(int v) { val v;}
};
/*** MyClass.cpp ***/
template<typename T>
int MyClass<T>::bar;
template class MyClass<Foo>;
template class MyClass<Bar>;
/*** main.cpp ***/
MyClass<Foo> * fool
MyClass<Foo> * foo2
MyClass<Bar> * barl
MyClass<Bar> * bar2
new MyClass<Foo>(10);
new MyClass<Foo>(15);
new MyClass<Bar>(20);
new MyClass<Bar>(35);
int fl fool->val; // will equal 15
int f2 foo2->val; // will equal 15
int bl barl->val; // will equal 35
int b2 bar2->val; // will equal 35
Java では、さまざまな型パラメータに関係なく、静的変数は MyClass のインスタンス間で共有されます。
Java ジェネリックと C++ テンプレートには他にも多くの違いがあります。これらには次のものが含まれます。
- C++ テンプレートでは、int などのプリミティブ型を使用できます。Javaは、代わりに整数を使用することはできませんし、使用する必要があります。
- Javaでは、テンプレートのタイプパラメーターを特定のタイプに制限できます。たとえば、genericsを使用してcarddeckを実装し、型パラメーターがcardgameから拡張する必要があることを指定する場合があります。
- C ++では、型パラメーターをインスタンス化できますが、Javaはこれをサポートしていません。
- Javaでは、タイプパラメーター(つまり、MyClassのFoo)を静的な方法と変数に使用することはできません。C++ では、これらのクラスは異なるため、型パラメータは静的メソッドと変数に使用できます。
- Java では、型パラメータに関係なく、MyClass のすべてのインスタンスは同じ型です。型パラメータは実行時に消去されます。C++ では、型パラメータが異なるインスタンスは異なる型です。
テンプレートはマクロ システムにほかなりません。シンタックスシュガー。これらは実際のコンパイル前に完全に展開されます (または、少なくともコンパイラはそのように動作します)。
例:
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++ で書き直そうとすると、テンプレートが無限に拡張されるというエラーが出てコンパイルできません。