C ++とJavaの「ジェネリック」タイプの違いは何ですか?


回答:


144

それらの間には大きな違いがあります。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のジェネリックスはタイプセーフを支援します。


27
彼はそれが単なる手の込んだ構文糖であるということは完全に正しい。
alphazero 2011

31
それは純粋に構文上の砂糖ではありません。コンパイラーは、この情報を使用してタイプを検査します。実行時に情報が利用できない場合でも、コンパイルされたものを単に「構文糖」と呼ぶことはありません。それを呼ぶなら、Cはアセンブリの構文糖であり、それはマシンコードの構文糖です:)
dtech

42
構文糖は便利だと思います。
poitroae 2013

5
ジェネリックをインスタンス化するために使用できる主要な違いを逃しました。c ++では、テンプレート<int N>を使用して、それをインスタンス化するために使用される任意の数値に対して異なる結果を得ることができます。コンパイル時のメタプログラミングに使用されます。の答えのように:stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal 2013

2
またはの形式で、「タイプを指定」する必要ありませ。正解は不正解extendssuper
ローンの侯爵

124

Java GenericsはC ++テンプレートとは大きく異なります。

基本的にC ++では、テンプレートは基本的に栄光のプリプロセッサ/マクロセット(注:類推を理解できない人もいるので、テンプレート処理がマクロであるとは言っていません)。Javaでは、それらは基本的にオブジェクトのボイラープレートキャストを最小限に抑えるための構文糖です。これは、C ++テンプレートとJavaジェネリックスのかなりまともな紹介です。

この点について詳しく説明します。C++テンプレートを使用する場合、基本的には、#defineマクロを使用する場合と同じように、コードの別のコピーを作成します。これによりint、配列のサイズなどを決定するテンプレート定義にパラメーターを設定するなどのことができます。

Javaはそのように動作しません。Javaでは、すべてのオブジェクトがjava.lang.Objectから拡張されるため、Genericsより前のコードは次のようになります。

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 Genericsです。オブジェクトをキャストするためのラッパーです。これは、Java Genericsが洗練されていないためです。彼らは型消去を使用します。この決定が行われたのは、Java Genericsの登場が遅れたため、下位互換性を壊したくなかったためです(a Map<String, String>はいつでも使用できます)Mapが呼び出されたは)。型消去が使用されていないところ.NET / C#にこれを比較し、その差異のすべての種類につながる(例えば、あなたがプリミティブ型を使用してすることができますIEnumerableし、IEnumerable<T>お互いに何の関係を負いません)。

また、Java 5+コンパイラーでコンパイルされたジェネリックを使用するクラスは、JDK 1.4で使用できます(Java 5+を必要とする他の機能またはクラスを使用しない場合)。

これが、Java Genericsが構文糖と呼ばれる理由です。ます。

しかし、ジェネリックの作成方法に関するこの決定は非常に大きな影響を与えるため、Javaジェネリックについての(すばらしい)JavaジェネリックFAQは、人々が持つ多くの多くの質問に答えるようになっています。

C ++テンプレートには、Java Genericsにはないいくつかの機能があります。

  • プリミティブ型引数の使用。

    例えば:

    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 ++のサブセットになりません。


1
それらは概念的に同等ではありません。好奇心旺盛に繰り返されるテンプレートパターンが最も良い例です。次善の策は、ポリシー指向の設計です。3番目に優れているのは、C ++では山かっこで整数を渡すことができるという事実です(myArray <5>)。
Max Lybbert、2009年

1
いいえ、それらは概念的に同等ではありません。概念にはいくつかの重複がありますが、多くはありません。どちらの方法でもList <T>を作成できますが、それで十分です。C ++テンプレートはさらに先を行きます。
2009年

5
型消去の問題は、の下位互換性以上のものを意味することに注意してくださいMap map = new HashMap<String, String>。これは、古いコードを新しいJVMにデプロイでき、バイトコードが類似しているため実行できることを意味します。
ユヴァルアダム

1
「基本的には栄光のプリプロセッサ/マクロ」と言ったことに気づくでしょう。各テンプレート宣言が(Java / C#とは対照的に)より多くのコードを作成するため、これは類推でした。
cletus 2009年

4
テンプレートコードは、コピーアンドペーストとは大きく異なります。:あなたはマクロ展開の観点から考えるならば、遅かれ早かれ、あなたは、このいずれかのように微妙なバグに見舞われるでしょうwomble.decadentplace.org.uk/c++/...
ネマニャTrifunovic

86

C ++にはテンプレートがあります。Javaにはジェネリックがあり、C ++テンプレートのように見えますが、非常に異なります。

テンプレートは、その名前が示すように、コンパイラーに(それを待つ...)テンプレートを提供することによって機能します。テンプレートは、テンプレートパラメーターを入力することでタイプセーフなコードを生成するために使用できます。

ジェネリックスは、私が理解しているように、逆の方法で機能します。型パラメーターはコンパイラーによって使用され、それらを使用するコードがタイプセーフであることを確認しますが、結果のコードは型なしで生成されます。

C ++テンプレートは非常に優れたマクロシステムであり、Javaジェネリックはタイプキャストを自動生成するツールであると考えてください。

 


4
これはかなり良い、簡潔な説明です。私が作りたくなる誘惑の1つは、Javaジェネリックスが安全であることが保証されているタイプキャストを自動的に生成するためのツールであることです(条件付き)。いくつかの点で、それらはC ++に関連していますconst。C ++のオブジェクトはconstconst-nessがキャストされない限り、ポインターを介して変更されません。同様に、Javaのジェネリック型によって作成された暗黙のキャストは、型パラメーターがコードのどこかに手動でキャストされない限り、「安全」であることが保証されています。
ローレンスゴンサルベス2010年

16

C ++テンプレートのもう1つの機能は、Javaジェネリックにはない特化です。これにより、特定のタイプに対して異なる実装を行うことができます。したがって、たとえば、intの高度に最適化されたバージョンを保持しながら、残りの型の汎用バージョンを保持できます。または、ポインタ型と非ポインタ型の異なるバージョンを使用できます。これは、ポインターが渡されたときに逆参照されたオブジェクトを操作する場合に便利です。


1
+1テンプレートの特殊化は、コンパイル時のメタプログラミングにとって非常に重要です-この違い自体により、Javaジェネリックはそれほど強力ではなくなります
Faisal Vali

13

このトピックの優れた説明は、Javaジェネリックとコレクション 、Philips WadlerによるMaurice Naftalinにあります。私はこの本を強くお勧めします。引用するには:

JavaのジェネリックスはC ++のテンプレートに似ています。...構文は意図的に類似しており、セマンティクスは意図的に異なります。...意味的には、Javaジェネリックは消去によって定義され、C ++テンプレートは拡張によって定義されます。

ここで完全な説明を読んでください。

代替テキスト
(ソース:oreilly.com


5

基本的に、AFAIK、C ​​++テンプレートは各タイプのコードのコピーを作成しますが、Javaジェネリックはまったく同じコードを使用します。

はい、あなた C ++テンプレートがJavaジェネリックの概念と同等であると言うことができます(より適切にはJavaジェネリックは概念のC ++と同等であると言うことですが)

C ++のテンプレートメカニズムに精通している場合、ジェネリックスは類似していると思うかもしれませんが、類似性は表面的なものです。ジェネリックスは、特殊化ごとに新しいクラスを生成することも、「テンプレートメタプログラミング」を許可することもありません。

から:Java Generics


3

Java(およびC#)ジェネリックは、単純な実行時の型置換メカニズムのようです。
C ++テンプレートはコンパイル時の構成要素であり、ニーズに合わせて言語を変更する方法を提供します。これらは実際には、コンパイラがコンパイル中に実行する純粋に機能的な言語です。


3

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では可能だとは思いません。


2
Javaにポインタがないためかもしれません。より良い例で説明できますか?
Bhavuk Mathur 2016

2

テンプレートで新しいタイプを作成し、ジェネリックで既存のタイプを制限します。


2
あなたの説明はとても簡単です!そして、トピックをよく理解している人にとっては完全に理にかなっています。しかし、それをまだ理解していない人にとっては、それはあまり役に立ちません。(SOで質問する人のケースはどれですか、それを得ましたか?)
Jakub

1

@キース:

そのコードは実際には間違っており、小さな不具合(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
なぜこれがコメントではなく回答なのですか?
Laurence Gonsalves

3
@Laurence:スタックオーバーフローにコメントが実装されるずっと前に投稿されたため。もう1つの理由は、コメントだけではなく、質問への回答でもあるためです。上記のコードのようなものはJavaでは不可能です。
Konrad Rudolph、2010年

1

以下の答えは、書籍 『クラッキングザコーディングインタビューソリューション』から第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は代わりにIntegerを使用できません。
  • Javaでは、テンプレートのタイプパラメータを特定のタイプに制限できます。たとえば、ジェネリックを使用してCardDeckを実装し、typeパラメータをCardGameから拡張する必要があることを指定できます。
  • C ++では、型パラメーターをインスタンス化できますが、Javaではこれをサポートしていません。
  • Javaでは、型パラメーター(つまり、MyClassのFoo)はMyClassとMyClassの間で共有されるため、静的メソッドと変数には使用できません。C ++では、これらのクラスが異なるため、静的メソッドおよび変数に型パラメーターを使用できます。
  • Javaでは、型パラメーターに関係なく、MyClassのすべてのインスタンスは同じ型です。型パラメーターは実行時に消去されます。C ++では、異なる型パラメーターを持つインスタンスは異なる型です。

0

テンプレートはマクロシステムにすぎません。構文糖。それらは実際のコンパイルの前に完全に拡張されます(または、少なくとも、コンパイラはあたかもそうだったかのように動作します)。

例:

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 ++で書き直そうとすると、テンプレートが無限に拡張されてしまうため、コンパイルできません。


これは3歳ですが、とにかく応答します。あなたの意見はわかりません。Javaがそのコメント付きの行に対してエラーを生成する理由は、引数が異なる2つのA(AとCons <A>)を期待する関数を呼び出すためであり、これは非常に基本的であり、ジェネリックが含まれていない場合にも発生します。C ++もそれを行います。それとは別に、このコードは本当に恐ろしいので私に癌を与えました。ただし、C ++でも同じように実行できます。もちろん、C ++はJavaではないので変更する必要がありますが、これはC ++のテンプレートの利点ではありません。
クロックタウン2016

@clocktownいいえ、C ++ではできません。それを許す量の変更はありません。そして、それはC ++テンプレートの欠点です。
MigMit 2016

あなたのコードが何をすることになっていたか-異なる長さについて警告-それはしません。コメントアウトした例では、引数が一致しないためにエラーが発生するだけです。これはC ++でも機能します。C ++とJavaでは、意味的に同等でこの混乱よりもはるかに優れたコードを入力できます。
クロックタウン

します。長さが異なるため、引数は正確に一致しません。C ++ではできません。
MigMit 16

0

ここでaskanydifferenceを引用したいと思います:

C ++とJavaの主な違いは、プラットフォームへの依存性にあります。C ++はプラットフォームに依存する言語ですが、Javaはプラットフォームに依存しない言語です。

上記のステートメントは、C ++が真のジェネリック型を提供できる理由です。Javaには厳密なチェックがあるため、C ++で許可されている方法でジェネリックを使用することはできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.