Jawaban:
Saya akan menambahkan suara saya ke kebisingan dan mencoba menjelaskan semuanya:
List<Person> foo = new List<Person>();
dan kemudian kompiler akan mencegah Anda memasukkan hal-hal yang tidak ada Person
dalam daftar.
Di belakang layar, kompiler C # hanya menempatkan List<Person>
ke dalam file .NET dll, tetapi pada saat runtime kompiler JIT berjalan dan membangun seperangkat kode baru, seolah-olah Anda telah menulis kelas daftar khusus hanya untuk memuat orang - sesuatu seperti ListOfPerson
.
Manfaat dari ini adalah membuatnya sangat cepat. Tidak ada casting atau hal-hal lain, dan karena dll mengandung informasi bahwa ini adalah Daftar Person
, kode lain yang melihatnya nanti menggunakan refleksi dapat mengatakan bahwa itu berisi Person
objek (sehingga Anda mendapatkan intellisense dan sebagainya).
Kelemahan dari ini adalah bahwa kode C # 1.0 dan 1.1 yang lama (sebelum mereka menambahkan obat generik) tidak memahami yang baru ini List<something>
, jadi Anda harus secara manual mengubah hal-hal kembali ke yang lama List
untuk dioperasi. Ini bukan masalah besar, karena kode biner C # 2.0 tidak kompatibel. Satu-satunya saat ini akan terjadi adalah jika Anda memutakhirkan beberapa kode C # 1.0 / 1.1 menjadi C # 2.0
ArrayList<Person> foo = new ArrayList<Person>();
Di permukaan tampak sama, dan itu semacam. Kompiler juga akan mencegah Anda memasukkan hal-hal yang tidak ada Person
dalam daftar.
Perbedaannya adalah apa yang terjadi di balik layar. Tidak seperti C #, Java tidak pergi dan membangun khusus ListOfPerson
- hanya menggunakan dataran lama ArrayList
yang selalu ada di Jawa. Ketika Anda mendapatkan hal-hal dari array, Person p = (Person)foo.get(1);
tarian-casting yang biasa masih harus dilakukan. Kompiler ini menyelamatkan Anda dari penekanan tombol, tetapi speed hit / casting masih terjadi seperti biasanya.
Ketika orang menyebutkan "Ketik Penghapusan" inilah yang mereka bicarakan. Kompiler memasukkan gips untuk Anda, dan kemudian 'menghapus' fakta bahwa itu dimaksudkan untuk menjadi daftar Person
bukan hanyaObject
Manfaat dari pendekatan ini adalah bahwa kode lama yang tidak mengerti obat generik tidak perlu dipedulikan. Itu masih berurusan dengan yang sama ArrayList
seperti dulu. Ini lebih penting di dunia java karena mereka ingin mendukung kompilasi kode menggunakan Java 5 dengan obat generik, dan menjalankannya di 1.4 lama atau JVM sebelumnya, yang sengaja memutuskan untuk tidak mengganggu dengan microsoft.
Kelemahannya adalah kecepatan yang saya sebutkan sebelumnya, dan juga karena tidak ada ListOfPerson
pseudo-class atau apa pun yang masuk ke file .class, kode yang melihatnya nanti (dengan refleksi, atau jika Anda menariknya keluar dari koleksi lain di mana itu telah dikonversi menjadi Object
atau lebih) tidak dapat mengatakan dengan cara apa pun bahwa itu dimaksudkan untuk menjadi daftar yang hanya berisi Person
dan bukan sembarang daftar array lainnya.
std::list<Person>* foo = new std::list<Person>();
Sepertinya C # dan generik Java, dan itu akan melakukan apa yang menurut Anda harus dilakukan, tetapi di balik layar hal-hal yang berbeda terjadi.
Ini memiliki yang paling umum dengan C # generics dalam hal itu membangun khusus pseudo-classes
daripada hanya membuang informasi jenis seperti java, tetapi itu adalah ketel ikan yang berbeda.
Baik C # dan Java menghasilkan output yang dirancang untuk mesin virtual. Jika Anda menulis beberapa kode yang memiliki Person
kelas di dalamnya, dalam kedua kasus beberapa informasi tentang Person
kelas akan masuk ke file .dll atau .class, dan JVM / CLR akan melakukan hal-hal dengan ini.
C ++ menghasilkan kode biner x86 mentah. Semuanya bukan objek, dan tidak ada mesin virtual mendasar yang perlu diketahui tentang sebuah Person
kelas. Tidak ada tinju atau unboxing, dan fungsi tidak harus milik kelas, atau memang apa pun.
Karena itu, kompiler C ++ tidak membatasi apa yang dapat Anda lakukan dengan templat - pada dasarnya kode apa pun yang dapat Anda tulis secara manual, Anda bisa mendapatkan templat yang bisa dituliskan untuk Anda.
Contoh paling jelas adalah menambahkan hal-hal:
Di C # dan Java, sistem generik perlu mengetahui metode apa yang tersedia untuk sebuah kelas, dan perlu meneruskannya ke mesin virtual. Satu-satunya cara untuk mengatakannya adalah dengan meng-coding kelas yang sebenarnya, atau menggunakan antarmuka. Sebagai contoh:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
Kode itu tidak akan dikompilasi dalam C # atau Java, karena tidak tahu bahwa jenisnya T
sebenarnya menyediakan metode yang disebut Nama (). Anda harus mengatakannya - dalam C # seperti ini:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
Dan kemudian Anda harus memastikan hal-hal yang Anda lewati untuk addNames mengimplementasikan antarmuka IHasName dan sebagainya. Sintaks java berbeda ( <T extends IHasName>
), tetapi menderita masalah yang sama.
Kasus 'klasik' untuk masalah ini sedang mencoba untuk menulis fungsi yang melakukan ini
string addNames<T>( T first, T second ) { return first + second; }
Anda tidak dapat benar-benar menulis kode ini karena tidak ada cara untuk mendeklarasikan antarmuka dengan +
metode di dalamnya. Kamu gagal.
C ++ tidak memiliki masalah ini. Kompiler tidak peduli tentang meneruskan tipe ke VM mana pun - jika kedua objek Anda memiliki fungsi .Name (), ia akan dikompilasi. Jika tidak, itu tidak akan terjadi. Sederhana.
Jadi, begitulah :-)
int addNames<T>( T first, T second ) { return first + second; }
dalam C #. Tipe generik dapat dibatasi untuk kelas bukan antarmuka, dan ada cara untuk mendeklarasikan kelas dengan +
operator di dalamnya.
C ++ jarang menggunakan terminologi "generik". Alih-alih, kata "templat" digunakan dan lebih akurat. Template menjelaskan satu teknik untuk mencapai desain generik.
Templat C ++ sangat berbeda dari yang diterapkan oleh C # dan Java karena dua alasan utama. Alasan pertama adalah bahwa templat C ++ tidak hanya membolehkan argumen tipe waktu kompilasi tetapi juga argumen nilai-nilai waktu kompilasi: templat dapat diberikan sebagai bilangan bulat atau bahkan tanda tangan fungsi. Ini berarti Anda dapat melakukan beberapa hal yang cukup funky pada waktu kompilasi, mis. Perhitungan:
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;
Kode ini juga menggunakan fitur lain yang dibedakan dari templat C ++, yaitu spesialisasi templat. Kode mendefinisikan satu templat kelas, product
yang memiliki satu argumen nilai. Itu juga mendefinisikan spesialisasi untuk template yang digunakan setiap kali argumen mengevaluasi ke 1. Ini memungkinkan saya untuk mendefinisikan rekursi atas definisi template. Saya percaya bahwa ini pertama kali ditemukan oleh Andrei Alexandrescu .
Spesialisasi template penting untuk C ++ karena memungkinkan perbedaan struktural dalam struktur data. Templat secara keseluruhan adalah sarana untuk menyatukan antarmuka antar tipe. Namun, meskipun ini diinginkan, semua jenis tidak dapat diperlakukan sama di dalam implementasi. Templat C ++ memperhitungkan hal ini. Perbedaan ini sangat mirip dengan yang dibuat OOP antara antarmuka dan implementasi dengan metode virtual utama.
Templat C ++ sangat penting untuk paradigma pemrograman algoritmiknya. Sebagai contoh, hampir semua algoritma untuk wadah didefinisikan sebagai fungsi yang menerima jenis wadah sebagai jenis templat dan memperlakukannya secara seragam. Sebenarnya, itu tidak sepenuhnya benar: C ++ tidak bekerja pada wadah tetapi pada rentang yang ditentukan oleh dua iterator, menunjuk ke awal dan di belakang ujung wadah. Dengan demikian, seluruh konten dibatasi oleh iterator: begin <= elements <end.
Menggunakan iterator sebagai ganti dari wadah berguna karena memungkinkan untuk beroperasi pada bagian-bagian dari suatu wadah dan bukan pada keseluruhannya.
Fitur lain yang membedakan C ++ adalah kemungkinan spesialisasi parsial untuk templat kelas. Ini agak terkait dengan pencocokan pola pada argumen dalam Haskell dan bahasa fungsional lainnya. Sebagai contoh, mari kita pertimbangkan kelas yang menyimpan elemen:
template <typename T>
class Store { … }; // (1)
Ini berfungsi untuk semua jenis elemen. Tetapi katakanlah kita dapat menyimpan pointer lebih efisien daripada tipe lainnya dengan menerapkan beberapa trik khusus. Kita dapat melakukan ini dengan mengkhususkan sebagian untuk semua jenis pointer:
template <typename T>
class Store<T*> { … }; // (2)
Sekarang, setiap kali kita membuat contoh templat wadah untuk satu jenis, definisi yang sesuai digunakan:
Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
Anders Hejlsberg sendiri menggambarkan perbedaan di sini " Generik dalam C #, Java, dan C ++ ".
Sudah ada banyak jawaban bagus tentang apa perbedaannya, jadi izinkan saya memberikan perspektif yang sedikit berbeda dan menambahkan alasannya .
Seperti yang sudah dijelaskan, perbedaan utama adalah penghapusan tipe , yaitu kenyataan bahwa kompiler Java menghapus tipe generik dan mereka tidak berakhir di bytecode yang dihasilkan. Namun, pertanyaannya adalah: mengapa ada orang yang melakukan itu? Itu tidak masuk akal! Atau apakah itu?
Nah, apa alternatifnya? Jika Anda tidak menerapkan generik dalam bahasa, di mana tidak Anda menerapkannya? Dan jawabannya adalah: di Mesin Virtual. Yang memecah kompatibilitas ke belakang.
Ketik penghapusan, di sisi lain, memungkinkan Anda untuk mencampur klien umum dengan perpustakaan non-generik. Dengan kata lain: kode yang dikompilasi di Java 5 masih dapat digunakan untuk Java 1.4.
Microsoft, bagaimanapun, memutuskan untuk memundurkan kompatibilitas untuk obat generik. Itu sebabnya .NET Generics "lebih baik" daripada Java Generics.
Tentu saja, Sun bukan orang idiot atau pengecut. Alasan mengapa mereka "ketakutan", adalah bahwa Jawa secara signifikan lebih tua dan lebih luas daripada. NET ketika mereka memperkenalkan obat generik. (Mereka diperkenalkan kira-kira pada saat yang sama di kedua dunia.) Memecah kompatibilitas ke belakang akan sangat menyebalkan.
Dengan kata lain: di Jawa, Generik adalah bagian dari Bahasa (yang berarti mereka hanya berlaku untuk Jawa, bukan ke bahasa lain), dalam. NET mereka adalah bagian dari Mesin Virtual (yang berarti mereka berlaku untuk semua bahasa, tidak hanya C # dan Visual Basic.NET).
Bandingkan ini dengan .NET fitur seperti LINQ, ekspresi lambda, inferensi tipe variabel lokal, tipe anonim dan pohon ekspresi: ini semua adalah fitur bahasa . Itulah sebabnya ada perbedaan tipis antara VB.NET dan C #: jika fitur-fitur itu adalah bagian dari VM, mereka akan sama dalam semua bahasa. Tetapi CLR tidak berubah: masih sama di. NET 3.5 SP1 seperti di. NET 2.0. Anda dapat mengkompilasi program C # yang menggunakan LINQ dengan kompiler .NET 3.5 dan masih menjalankannya di .NET 2.0, asalkan Anda tidak menggunakan perpustakaan .NET 3.5. Itu tidak akan bekerja dengan generik dan .NET 1.1, tetapi itu akan bekerja dengan Java dan Java 1.4.
ArrayList<T>
dapat dipancarkan sebagai jenis yang dinamai secara internal dengan Class<T>
bidang statis (tersembunyi) . Selama versi baru lib generik telah digunakan dengan kode 1,5+ byte, itu akan dapat berjalan pada 1,4-JVMs.
Tindak lanjuti posting saya sebelumnya.
Template adalah salah satu alasan utama mengapa C ++ gagal begitu parah di intellisense, terlepas dari IDE yang digunakan. Karena spesialisasi templat, IDE tidak pernah dapat benar-benar yakin apakah anggota yang diberikan ada atau tidak. Mempertimbangkan:
template <typename T>
struct X {
void foo() { }
};
template <>
struct X<int> { };
typedef int my_int_type;
X<my_int_type> a;
a.|
Sekarang, kursor berada pada posisi yang ditunjukkan dan sangat sulit bagi IDE untuk mengatakan pada titik itu jika, dan apa, anggota a
miliki. Untuk bahasa lain parsing akan langsung tetapi untuk C ++, cukup banyak evaluasi diperlukan sebelumnya.
Itu semakin buruk. Bagaimana jika my_int_type
didefinisikan di dalam templat kelas juga? Sekarang tipenya akan tergantung pada argumen tipe lain. Dan di sini, bahkan kompiler gagal.
template <typename T>
struct Y {
typedef T my_type;
};
X<Y<int>::my_type> b;
Setelah sedikit berpikir, seorang programmer akan menyimpulkan bahwa kode ini adalah sama seperti di atas: Y<int>::my_type
resolve ke int
, oleh karena itu b
harus jenis yang sama seperti a
, kan?
Salah. Pada titik di mana kompiler mencoba menyelesaikan pernyataan ini, sebenarnya Y<int>::my_type
belum tahu ! Karena itu, tidak tahu bahwa ini adalah tipe. Ini bisa berupa sesuatu yang lain, misalnya fungsi anggota atau bidang. Ini mungkin menimbulkan ambiguitas (meskipun tidak dalam kasus ini), oleh karena itu kompiler gagal. Kita harus mengatakannya secara eksplisit bahwa kita merujuk pada nama tipe:
X<typename Y<int>::my_type> b;
Sekarang, kompilasi kode. Untuk melihat bagaimana ambiguitas muncul dari situasi ini, pertimbangkan kode berikut:
Y<int>::my_type(123);
Pernyataan kode ini sangat valid dan memberitahu C ++ untuk menjalankan pemanggilan fungsi Y<int>::my_type
. Namun, jika my_type
bukan fungsi melainkan tipe, pernyataan ini masih akan valid dan melakukan pemeran khusus (pemeran fungsi-gaya) yang sering kali merupakan doa konstruktor. Kompiler tidak dapat menentukan yang kami maksud sehingga kami harus membuat ambigu di sini.
Java dan C # memperkenalkan generik setelah rilis bahasa pertama mereka. Namun, ada perbedaan dalam bagaimana perpustakaan inti berubah ketika obat generik diperkenalkan. Generik C # bukan hanya kompiler ajaib dan karenanya tidak mungkin untuk menghasilkan kelas perpustakaan yang ada tanpa merusak kompatibilitas ke belakang.
Sebagai contoh, di Jawa yang ada Collections Framework itu benar-benar genericised . Java tidak memiliki versi koleksi kelas generik dan non-generik. Dalam beberapa hal ini jauh lebih bersih - jika Anda perlu menggunakan koleksi di C # benar-benar ada sedikit alasan untuk menggunakan versi non-generik, tetapi kelas-kelas warisan tetap ada, mengacaukan lanskap.
Perbedaan penting lainnya adalah kelas Enum di Jawa dan C #. Java's Enum memiliki definisi yang agak berliku:
// java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
(lihat penjelasan Angelika Langer yang sangat jelas mengapa hal ini terjadi. Pada dasarnya, ini berarti Java dapat memberikan tipe akses aman dari string ke nilai Enumnya:
// Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");
Bandingkan ini dengan versi C #:
// Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");
Karena Enum sudah ada di C # sebelum generik diperkenalkan ke bahasa, definisi tidak dapat berubah tanpa melanggar kode yang ada. Jadi, seperti koleksi, ia tetap berada di perpustakaan inti di negara warisan ini.
ArrayList
untuk List<T>
dan memasukkannya ke dalam namespace baru. Faktanya adalah, jika ada kelas yang muncul dalam kode sumber karena ArrayList<T>
akan menjadi kompiler yang berbeda yang dihasilkan nama kelas dalam kode IL, sehingga tidak ada konflik nama yang dapat terjadi.
11 bulan terlambat, tapi saya pikir pertanyaan ini siap untuk beberapa hal Java Wildcard.
Ini adalah fitur sintaksis Java. Misalkan Anda memiliki metode:
public <T> void Foo(Collection<T> thing)
Dan misalkan Anda tidak perlu merujuk ke tipe T di tubuh metode. Anda mendeklarasikan nama T dan kemudian hanya menggunakannya sekali, jadi mengapa Anda harus memikirkan nama untuk itu? Sebagai gantinya, Anda dapat menulis:
public void Foo(Collection<?> thing)
Tanda tanya meminta kompiler untuk berpura-pura bahwa Anda mendeklarasikan parameter tipe normal bernama yang hanya perlu muncul sekali di tempat itu.
Tidak ada yang dapat Anda lakukan dengan wildcard yang tidak dapat Anda lakukan dengan parameter tipe bernama (yang selalu dilakukan dalam C ++ dan C #).
class Foo<T extends List<?>>
dan gunakan Foo<StringList>
tetapi di C # Anda harus menambahkan parameter tipe ekstra: class Foo<T, T2> where T : IList<T2>
dan menggunakan clunky Foo<StringList, String>
.
Wikipedia memiliki artikel bagus yang membandingkan templat Java / C # generics dan Java generics / C ++ . The Artikel utama pada Generics tampaknya sedikit berantakan tetapi memiliki beberapa info yang baik di dalamnya.
Keluhan terbesar adalah penghapusan tipe. Dalam hal itu, obat generik tidak diberlakukan saat runtime. Berikut ini tautan ke beberapa dokumen Sun tentang masalah ini .
Generik diimplementasikan oleh tipe erasure: informasi tipe generik hanya hadir pada waktu kompilasi, setelah itu dihapus oleh kompiler.
Templat C ++ sebenarnya jauh lebih kuat daripada C # dan rekan-rekan Java mereka dievaluasi pada waktu kompilasi dan mendukung spesialisasi. Hal ini memungkinkan untuk Pemrograman Meta Templat dan membuat kompiler C ++ setara dengan mesin Turing (yaitu selama proses kompilasi Anda dapat menghitung apa pun yang dapat dihitung dengan mesin Turing).
Sepertinya, di antara proposal yang sangat menarik, ada satu tentang penyempurnaan obat generik dan melanggar kompatibilitas:
Saat ini, obat generik diimplementasikan menggunakan erasure, yang berarti bahwa informasi jenis generik tidak tersedia saat runtime, yang membuat beberapa jenis kode sulit untuk ditulis. Generik diimplementasikan dengan cara ini untuk mendukung kompatibilitas dengan kode non-generik yang lebih lama. Reified generics akan membuat informasi tipe generik tersedia saat runtime, yang akan memecah kode non-generik lama. Namun, Neal Gafter telah mengusulkan pembuatan jenis yang hanya dapat diverifikasi jika ditentukan, agar tidak merusak kompatibilitas.
NB: Saya tidak punya cukup poin untuk berkomentar, jadi silakan pindahkan ini sebagai komentar untuk jawaban yang tepat.
Bertentangan dengan kepercayaan populer, yang tidak pernah saya pahami dari mana asalnya, .net menerapkan obat generik sejati tanpa merusak kompatibilitas ke belakang, dan mereka menghabiskan upaya eksplisit untuk itu. Anda tidak perlu mengubah kode .net 1.0 non-generik Anda menjadi generik hanya untuk digunakan dalam .net 2.0. Baik daftar generik dan non-generik masih tersedia di .Net framework 2.0 bahkan hingga 4.0, hanya karena alasan kompatibilitas. Karenanya kode lama yang masih menggunakan ArrayList non-generik akan tetap berfungsi, dan menggunakan kelas ArrayList yang sama seperti sebelumnya. Kompatibilitas kode mundur selalu dipertahankan sejak 1.0 hingga sekarang ... Jadi, bahkan dalam .net 4.0, Anda masih harus memilih untuk menggunakan kelas non-generik mulai dari 1,0 BCL jika Anda memilih untuk melakukannya.
Jadi saya tidak berpikir java harus memundurkan kompatibilitas untuk mendukung generik yang sebenarnya.
ArrayList<Foo>
yang ingin diteruskan ke metode yang lebih lama yang seharusnya diisi ArrayList
dengan contoh Foo
. Jika sebuah ArrayList<foo>
bukan ArrayList
, bagaimana cara membuatnya?