Apa perbedaan antara tipe "generik" dalam C ++ dan Java?

Jawaban:

144

Ada perbedaan besar di antara mereka. Di C ++ Anda tidak harus menentukan kelas atau antarmuka untuk tipe generik. Itu sebabnya Anda dapat membuat fungsi dan kelas yang benar-benar generik, dengan peringatan dari pengetikan yang lebih longgar.

template <typename T> T sum(T a, T b) { return a + b; }

Metode di atas menambahkan dua objek dengan tipe yang sama, dan dapat digunakan untuk semua tipe T yang memiliki operator "+".

Di Java Anda harus menentukan tipe jika Anda ingin memanggil metode pada objek yang dilewati, seperti:

<T extends Something> T sum(T a, T b) { return a.add ( b ); }

Dalam C ++ fungsi generik / kelas hanya dapat didefinisikan dalam header, karena kompiler menghasilkan fungsi yang berbeda untuk jenis yang berbeda (yang dipanggil dengan). Jadi kompilasi lebih lambat. Di Jawa kompilasi tidak memiliki penalti besar, tetapi Java menggunakan teknik yang disebut "penghapusan" di mana tipe generik dihapus saat runtime, jadi pada saat runtime Jawa sebenarnya memanggil ...

Something sum(Something a, Something b) { return a.add ( b ); }

Jadi pemrograman generik di Jawa tidak benar-benar berguna, hanya sedikit sintaksis gula untuk membantu dengan konstruksi foreach baru.

EDIT: pendapat di atas tentang kegunaan ditulis oleh diri yang lebih muda. Generik Java membantu tentu saja dengan keamanan jenis.

Alexandru Nedelcu
sumber
27
Dia benar sekali bahwa itu hanyalah gula sintaksis yang rumit.
alphazero
31
Ini bukan gula sintaksis murni. Kompiler menggunakan informasi ini untuk memeriksa jenis. Meskipun informasi tidak tersedia saat runtime, saya tidak akan menyebut sesuatu yang dikompilasi menggunakan hanya "gula sintaksis". Jika Anda menyebutnya begitu, maka C hanyalah gula sintaksis untuk perakitan, dan itu hanya gula sintaksis untuk kode mesin :)
dtech
42
Saya pikir gula sintaksis bermanfaat.
poitroae
5
Anda melewatkan titik perbedaan utama, apa yang dapat Anda gunakan untuk instantiate generik. Dalam c ++ dimungkinkan untuk menggunakan template <int N> dan mendapatkan hasil berbeda untuk nomor apa pun yang digunakan untuk instantiate. Ini digunakan untuk mengkompilasi meta waktu progaming. Seperti jawaban di: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal
2
Anda tidak harus 'menentukan jenis', dalam bentuk salah satu extendsatau super. Jawabannya salah,
Marquis of Lorne
124

Java Generics yang secara besar-besaran yang berbeda untuk C ++ template.

Pada dasarnya di C ++ templat pada dasarnya adalah set preprocessor / makro yang dimuliakan ( Catatan: karena beberapa orang tampaknya tidak dapat memahami analogi, saya tidak mengatakan pemrosesan template adalah makro). Di Jawa mereka pada dasarnya sintaksis gula untuk meminimalkan pengecoran boilerplate dari Obyek. Berikut ini adalah pengantar yang cukup baik untuk template C ++ vs Java generics .

Untuk menguraikan hal ini: ketika Anda menggunakan template C ++, Anda pada dasarnya membuat salinan kode lainnya, sama seperti jika Anda menggunakan #definemakro. Ini memungkinkan Anda untuk melakukan hal-hal seperti memiliki intparameter dalam definisi templat yang menentukan ukuran array dan semacamnya.

Java tidak berfungsi seperti itu. Di Jawa semua objek diperluas dari java.lang.Object jadi, pra-Generik, Anda akan menulis kode seperti ini:

public class PhoneNumbers {
  private Map phoneNumbers = new HashMap();

  public String getPhoneNumber(String name) {
    return (String)phoneNumbers.get(name);
  }

  ...
}

karena semua tipe koleksi Java menggunakan Object sebagai tipe dasar mereka sehingga Anda bisa memasukkan apa pun di dalamnya. Java 5 berguling-guling dan menambahkan obat generik sehingga Anda dapat melakukan hal-hal seperti:

public class PhoneNumbers {
  private Map<String, String> phoneNumbers = new HashMap<String, String>();

  public String getPhoneNumber(String name) {
    return phoneNumbers.get(name);
  }

  ...
}

Dan itu semua Java Generics adalah: pembungkus untuk benda casting. Itu karena Java Generics tidak disempurnakan. Mereka menggunakan tipe erasure. Keputusan ini dibuat karena Java Generics datang sangat terlambat sehingga mereka tidak ingin merusak kompatibilitas (a Map<String, String>dapat digunakan kapan saja diperlukan Map). Bandingkan ini dengan .Net / C # di mana type erasure tidak digunakan, yang mengarah ke semua jenis perbedaan (misalnya Anda dapat menggunakan tipe primitif dan IEnumerabledan IEnumerable<T>tidak memiliki hubungan satu sama lain).

Dan kelas yang menggunakan generik yang dikompilasi dengan kompiler Java 5+ dapat digunakan di JDK 1.4 (dengan asumsi ia tidak menggunakan fitur atau kelas lain yang membutuhkan Java 5+).

Itu sebabnya Java Generics disebut gula sintaksis .

Tapi keputusan tentang bagaimana melakukan obat generik ini memiliki efek yang sangat besar sehingga (hebat) FAQ Java Generics telah muncul untuk menjawab banyak, banyak pertanyaan yang orang miliki tentang Java Generics.

Templat C ++ memiliki sejumlah fitur yang tidak dimiliki Java Generics:

  • Penggunaan argumen tipe primitif.

    Sebagai contoh:

    template<class T, int i>
    class Matrix {
      int T[i][i];
      ...
    }

    Java tidak mengizinkan penggunaan argumen tipe primitif dalam generik.

  • Penggunaan argumen tipe default , yang merupakan salah satu fitur yang saya lewatkan di Jawa tetapi ada alasan kompatibilitas belakang untuk ini;

  • Java memungkinkan pembatasan argumen.

Sebagai contoh:

public class ObservableList<T extends List> {
  ...
}

Perlu ditekankan bahwa permintaan templat dengan argumen berbeda adalah tipe yang berbeda. Mereka bahkan tidak membagikan anggota statis. Di Jawa, ini bukan masalahnya.

Selain perbedaan dengan obat generik, untuk kelengkapan, berikut ini adalah perbandingan dasar C ++ dan Java (dan yang lainnya ).

Dan saya juga bisa menyarankan Berpikir di Jawa . Sebagai seorang programmer C ++ banyak konsep seperti objek sudah menjadi sifat kedua tetapi ada perbedaan yang halus sehingga dapat bermanfaat untuk memiliki teks pengantar bahkan jika Anda membaca bagian-bagiannya.

Banyak hal yang akan Anda pelajari ketika mempelajari Java adalah semua perpustakaan (keduanya standar - yang ada di JDK - dan tidak standar, yang mencakup hal-hal yang biasa digunakan seperti Spring). Sintaks Java lebih verbose daripada sintaksis C ++ dan tidak memiliki banyak fitur C ++ (mis. Operator overloading, multiple inheritance, mekanisme destructor, dll) tetapi itu tidak secara ketat menjadikannya subset dari C ++ juga.

cletus
sumber
1
Mereka tidak setara dalam konsep. Contoh terbaik adalah pola templat berulang yang aneh. Yang terbaik kedua adalah desain yang berorientasi kebijakan. Yang ketiga terbaik adalah fakta bahwa C ++ memungkinkan bilangan integral dilewatkan dalam kurung sudut (myArray <5>).
Max Lybbert
1
Tidak, mereka tidak setara dalam konsep. Ada beberapa konsep yang tumpang tindih, tetapi tidak banyak. Keduanya memungkinkan Anda untuk membuat Daftar <T>, tetapi hanya sejauh itulah yang terjadi. Template C ++ jauh lebih maju.
jalf
5
Penting untuk dicatat bahwa masalah penghapusan tipe berarti lebih dari sekadar kompatibilitas Map map = new HashMap<String, String>. Ini berarti Anda dapat menggunakan kode baru pada JVM lama dan itu akan berjalan karena kesamaan dalam bytecode.
Yuval Adam
1
Anda akan perhatikan saya berkata "pada dasarnya preprocessor / makro yang dimuliakan". Itu analogi karena setiap deklarasi templat akan membuat lebih banyak kode (berbeda dengan Java / C #).
cletus
4
Kode templat sangat berbeda dari salin dan tempel. Jika Anda berpikir dalam hal ekspansi makro, cepat atau lambat Anda akan terkena bug halus seperti ini: womble.decadentplace.org.uk/c++/…
Nemanja Trifunovic
86

C ++ memiliki template. Java memiliki generik, yang terlihat agak seperti template C ++, tetapi mereka sangat, sangat berbeda.

Templat berfungsi, seperti namanya, dengan menyediakan kompiler dengan templat (tunggu ...) yang dapat digunakan untuk menghasilkan kode tipe-aman dengan mengisi parameter templat.

Generik, seperti yang saya mengerti, bekerja sebaliknya: parameter tipe digunakan oleh kompiler untuk memverifikasi bahwa kode yang menggunakannya adalah tipe-aman, tetapi kode yang dihasilkan dihasilkan tanpa tipe sama sekali.

Pikirkan template C ++ sebagai sistem makro yang sangat bagus , dan Java generics sebagai alat untuk menghasilkan typecast secara otomatis.

 

Shog9
sumber
4
Ini penjelasan yang cukup bagus dan singkat. Satu tweak saya akan tergoda untuk membuat adalah bahwa generik Java adalah alat untuk secara otomatis menghasilkan typecast yang dijamin aman (dengan beberapa kondisi). Dalam beberapa hal mereka terkait dengan C ++ const. Objek dalam C ++ tidak akan dimodifikasi melalui constpointer kecuali the const-ness dibuang. Demikian juga, gips implisit yang dibuat oleh tipe generik di Java dijamin "aman" kecuali parameter tipe secara manual dibuang di suatu tempat dalam kode.
Laurence Gonsalves
16

Fitur lain yang dimiliki templat C ++ yang tidak dimiliki generik Java adalah spesialisasi. Itu memungkinkan Anda untuk memiliki implementasi yang berbeda untuk tipe tertentu. Jadi, Anda dapat, misalnya, memiliki versi yang sangat optimal untuk int , sementara masih memiliki versi generik untuk jenis lainnya. Atau Anda dapat memiliki versi berbeda untuk tipe pointer dan non-pointer. Ini sangat berguna jika Anda ingin mengoperasikan objek dereferenced ketika menyerahkan pointer.

KeithB
sumber
1
+1 templat spesialisasi adalah sangat penting untuk metaprogramming waktu kompilasi - perbedaan ini dengan sendirinya membuat generik java yang jauh kurang kuat
Faisal Vali
13

Ada penjelasan yang bagus tentang topik ini dalam Java Generics and Collections Oleh Maurice Naftalin, Philip Wadler. Saya sangat merekomendasikan buku ini. Kutipan:

Generik di Jawa menyerupai templat di C ++. ... Sintaksnya sengaja sama dan semantiknya sengaja berbeda. ... Secara semantik, generik Java didefinisikan oleh penghapusan, sedangkan templat C ++ didefinisikan oleh ekspansi.

Silakan baca penjelasan lengkapnya di sini .

teks alternatif
(sumber: oreilly.com )

Julien Chastang
sumber
5

Pada dasarnya, templat AFAIK, C ++ membuat salinan kode untuk setiap jenis, sedangkan generik Java menggunakan kode yang persis sama.

Ya, Anda dapat mengatakan bahwa template C ++ setara dengan konsep generik Java (meskipun lebih tepat jika dikatakan generik Java setara dengan konsep C ++)

Jika Anda terbiasa dengan mekanisme template C ++, Anda mungkin berpikir bahwa generik serupa, tetapi kesamaannya dangkal. Generik tidak menghasilkan kelas baru untuk setiap spesialisasi, mereka juga tidak mengizinkan "metaprogramming template."

dari: Java Generics

OscarRyz
sumber
3

Java (dan C #) generik tampaknya merupakan mekanisme substitusi tipe run-time yang sederhana.
Templat C ++ adalah konstruk waktu kompilasi yang memberi Anda cara untuk memodifikasi bahasa yang sesuai dengan kebutuhan Anda. Mereka sebenarnya bahasa murni fungsional yang dijalankan oleh kompiler selama kompilasi.

Ferruccio
sumber
3

Keuntungan lain dari template C ++ adalah spesialisasi.

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); }

Sekarang, jika Anda memanggil jumlah dengan pointer, metode kedua akan dipanggil, jika Anda memanggil jumlah dengan objek non-pointer metode pertama akan dipanggil, dan jika Anda memanggil sumdengan Specialobjek, metode ketiga akan dipanggil. Saya tidak berpikir ini mungkin dengan Java.

KeithB
sumber
2
Mungkin karena Jawa tidak memiliki petunjuk .. !! dapatkah Anda menjelaskan dengan contoh yang lebih baik?
Bhavuk Mathur
2

Saya akan meringkasnya dalam satu kalimat: templat membuat tipe baru, generik membatasi tipe yang ada.

Marquis dari Lorne
sumber
2
Penjelasan Anda sangat singkat! Dan sangat masuk akal bagi orang yang memahami topik dengan baik. Tetapi bagi orang yang belum memahaminya, itu tidak banyak membantu. (Yang mana kasus seseorang bertanya pada SO, mengerti?)
Jakub
1

@Keith:

Kode itu sebenarnya salah dan terlepas dari gangguan yang lebih kecil ( templatedihilangkan, sintaksisasinya terlihat berbeda), spesialisasi parsial tidak bekerja pada templat fungsi, hanya pada templat kelas. Namun kode akan bekerja tanpa spesialisasi templat parsial, alih-alih menggunakan overloading lama:

template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Konrad Rudolph
sumber
2
Mengapa ini jawaban dan bukan komentar?
Laurence Gonsalves
3
@Laurence: untuk sekali, karena telah diposting jauh sebelum komentar diimplementasikan pada Stack Overflow. Untuk yang lain, karena itu bukan hanya komentar - itu juga jawaban untuk pertanyaan: sesuatu seperti kode di atas tidak mungkin di Jawa.
Konrad Rudolph
1

Jawaban di bawah ini dari buku Cracking The Coding Wawancara Solusi untuk Bab 13, yang menurut saya sangat bagus.

Penerapan Java generics berakar pada gagasan "type erasure: 'Teknik ini menghilangkan tipe parameter ketika kode sumber diterjemahkan ke bytecode Java Virtual Machine (JVM). Misalnya, Anda memiliki kode Java di bawah ini:

Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);

Selama kompilasi, kode ini ditulis ulang menjadi:

Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);

Penggunaan generik Java tidak banyak berubah tentang kemampuan kami; itu hanya membuat segalanya sedikit lebih cantik. Karena alasan ini, obat generik Java terkadang disebut "gula sintaksis: '.

Ini sangat berbeda dari C ++. Dalam C ++, templat pada dasarnya adalah kumpulan makro yang dimuliakan, dengan kompiler membuat salinan baru dari kode templat untuk setiap jenis. Bukti dari ini adalah fakta bahwa turunan dari MyClass tidak akan berbagi variabel statis dengan MyClass. Namun, contoh MyClass akan berbagi variabel statis.

/*** 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

Di Jawa, variabel statis dibagi di seluruh instance MyClass, terlepas dari parameter tipe yang berbeda.

Java generics dan C ++ templates memiliki sejumlah perbedaan lainnya. Ini termasuk:

  • Templat C ++ dapat menggunakan tipe primitif, seperti int. Java tidak bisa dan harus menggunakan Integer.
  • Di Jawa, Anda bisa membatasi parameter tipe templat menjadi tipe tertentu. Misalnya, Anda dapat menggunakan obat generik untuk mengimplementasikan CardDeck dan menentukan bahwa parameter tipe harus diperluas dari CardGame.
  • Dalam C ++, parameter type dapat dipakai, sedangkan Java tidak mendukung ini.
  • Di Jawa, parameter type (yaitu, Foo di MyClass) tidak dapat digunakan untuk metode dan variabel statis, karena ini akan dibagi antara MyClass dan MyClass. Dalam C ++, kelas-kelas ini berbeda, sehingga parameter tipe dapat digunakan untuk metode dan variabel statis.
  • Di Java, semua instance MyClass, terlepas dari parameter tipe mereka, adalah tipe yang sama. Parameter tipe dihapus saat runtime. Dalam C ++, instance dengan parameter tipe yang berbeda adalah tipe yang berbeda.
Jaycee
sumber
0

Template tidak lain adalah sistem makro. Gula sintaksis. Mereka sepenuhnya diperluas sebelum kompilasi aktual (atau, setidaknya, kompiler berperilaku seolah-olah itu adalah kasus).

Contoh:

Katakanlah kita menginginkan dua fungsi. Satu fungsi mengambil dua urutan (daftar, array, vektor, apa saja) angka, dan mengembalikan produk dalam mereka. Fungsi lain membutuhkan panjang, menghasilkan dua urutan panjang itu, meneruskannya ke fungsi pertama, dan mengembalikan hasilnya. Tangkapannya adalah kita mungkin melakukan kesalahan pada fungsi kedua, sehingga kedua fungsi ini tidak memiliki panjang yang sama. Kami membutuhkan kompiler untuk memperingatkan kami dalam hal ini. Tidak ketika program sedang berjalan, tetapi saat dikompilasi.

Di Jawa Anda dapat melakukan sesuatu seperti ini:

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");
        }
    }
}

Dalam C # Anda dapat menulis hal yang hampir sama. Cobalah untuk menulis ulang di C ++, dan itu tidak akan dikompilasi, mengeluh tentang perluasan templat yang tak terbatas.

MigMit
sumber
Oke, ini sudah 3 tahun, tetapi saya merespons. Saya tidak mengerti maksud Anda. Seluruh alasan Java menghasilkan Error untuk Line yang dikomentari adalah karena Anda akan memanggil fungsi yang mengharapkan dua A dengan Argumen yang berbeda (A dan Kontra <A>) dan ini sangat mendasar dan juga terjadi ketika tidak ada obat generik yang terlibat. C ++ juga melakukannya. Terlepas dari itu, kode ini memberiku kanker karena itu benar-benar mengerikan. Namun, Anda masih akan melakukannya seperti itu di C ++, Anda harus melakukan modifikasi tentu saja karena C ++ bukan Java, tetapi itu bukan kelebihannya dari C ++'s Templates.
clocktown
@clocktown no, Anda TIDAK BISA melakukannya di C ++. Tidak ada jumlah modifikasi yang memungkinkan. Dan itu adalah kelemahan dari template C ++.
MigMit
Apa yang seharusnya dilakukan Kode Anda - peringatkan tentang panjang yang berbeda - itu tidak dilakukan. Dalam contoh komentar Anda hanya menghasilkan kesalahan karena Argumen tidak cocok. Itu bekerja di C ++ juga. Anda bisa mengetikkan kode yang secara semantik setara dan jauh lebih baik daripada kekacauan ini di C ++ dan di Jawa.
clocktown
Itu benar. Argumen tidak sama persis karena panjangnya berbeda. Anda tidak dapat melakukannya di C ++.
MigMit
0

Saya ingin mengutip askanydifference di sini:

Perbedaan utama antara C ++ dan Java terletak pada ketergantungannya pada platform. Sementara, C ++ adalah bahasa yang bergantung pada platform, Java adalah bahasa yang bebas platform.

Pernyataan di atas adalah alasan mengapa C ++ mampu memberikan tipe generik yang benar. Sementara Java memang memiliki pemeriksaan ketat dan karenanya mereka tidak mengizinkan penggunaan obat generik seperti yang dilakukan C ++.

Piyush-Tanyakan Perbedaan Apa Pun
sumber