Apa perbedaan antara Generik di C # dan Java ... dan Templat di C ++? [Tutup]

203

Saya kebanyakan menggunakan Java dan obat generik relatif baru. Saya terus membaca bahwa Java membuat keputusan yang salah atau. NET memiliki implementasi yang lebih baik dll.

Jadi, apa perbedaan utama antara C ++, C #, Java dalam generik? Pro / kontra masing-masing?

pek
sumber

Jawaban:

364

Saya akan menambahkan suara saya ke kebisingan dan mencoba menjelaskan semuanya:

C # Generik memungkinkan Anda untuk mendeklarasikan sesuatu seperti ini.

List<Person> foo = new List<Person>();

dan kemudian kompiler akan mencegah Anda memasukkan hal-hal yang tidak ada Persondalam 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 Personobjek (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 Listuntuk 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

Java Generics memungkinkan Anda untuk mendeklarasikan sesuatu seperti ini.

ArrayList<Person> foo = new ArrayList<Person>();

Di permukaan tampak sama, dan itu semacam. Kompiler juga akan mencegah Anda memasukkan hal-hal yang tidak ada Persondalam daftar.

Perbedaannya adalah apa yang terjadi di balik layar. Tidak seperti C #, Java tidak pergi dan membangun khusus ListOfPerson- hanya menggunakan dataran lama ArrayListyang 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 Personbukan hanyaObject

Manfaat dari pendekatan ini adalah bahwa kode lama yang tidak mengerti obat generik tidak perlu dipedulikan. Itu masih berurusan dengan yang sama ArrayListseperti 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 ListOfPersonpseudo-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 Objectatau lebih) tidak dapat mengatakan dengan cara apa pun bahwa itu dimaksudkan untuk menjadi daftar yang hanya berisi Persondan bukan sembarang daftar array lainnya.

C ++ Templates memungkinkan Anda untuk mendeklarasikan sesuatu seperti ini

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-classesdaripada 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 Personkelas di dalamnya, dalam kedua kasus beberapa informasi tentang Personkelas 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 Personkelas. 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 Tsebenarnya 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 :-)

Orion Edwards
sumber
8
Pseudoclasess yang dihasilkan untuk tipe referensi di C # berbagi implementasi yang sama sehingga Anda tidak akan mendapatkan ListOfPeople secara persis. Periksa blogs.msdn.com/ericlippert/archive/2009/07/30/...
Piotr Czapla
4
Tidak, Anda tidak dapat mengkompilasi kode Java 5 menggunakan generik, dan menjalankannya pada 1.4 VM lama (setidaknya Sun JDK tidak mengimplementasikan ini. Beberapa alat pihak ketiga lakukan.) Yang dapat Anda lakukan adalah menggunakan 1,4 JAR yang dikompilasi sebelumnya dari 1,5 / 1,6 kode.
finnw
4
Saya keberatan dengan pernyataan bahwa Anda tidak dapat menulis 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.
Mashmagar
4
@AlexanderMalakhov itu tidak idiomatis dengan sengaja. Intinya bukan untuk mendidik tentang idiom C ++, tetapi untuk menggambarkan bagaimana potongan kode yang tampak sama ditangani secara berbeda oleh setiap bahasa. Tujuan ini akan lebih sulit untuk mencapai kode yang terlihat lebih berbeda
Orion Edwards
3
@ php Saya setuju pada prinsipnya, tetapi jika saya menulis potongan itu dalam C + + idiomatik, itu akan jauh lebih dimengerti oleh pengembang C # / Java, dan karena itu (saya percaya) akan melakukan pekerjaan yang lebih buruk dalam menjelaskan perbedaannya. Mari kita sepakat untuk tidak setuju pada yang satu ini :-)
Orion Edwards
61

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, productyang 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*.
Konrad Rudolph
sumber
Terkadang saya berharap fitur generik di .net dapat memungkinkan hal selain jenis digunakan sebagai kunci. Jika array tipe nilai adalah bagian dari Kerangka (saya terkejut mereka tidak, dengan cara, mengingat kebutuhan untuk berinteraksi dengan API lama yang menanamkan array berukuran tetap di dalam struktur), akan berguna untuk mendeklarasikan kelas yang berisi beberapa item individual dan kemudian array tipe-nilai yang ukurannya merupakan parameter generik. Seperti itu, yang paling dekat bisa datang adalah memiliki objek kelas yang menyimpan item individual dan kemudian juga memegang referensi ke objek terpisah yang memegang array.
supercat
@supercat Jika Anda berinteraksi dengan legacy API idenya adalah menggunakan marshalling (yang dapat dijelaskan melalui atribut). CLR tidak memiliki array ukuran tetap, jadi memiliki argumen template non-tipe tidak akan membantu di sini.
Konrad Rudolph
Saya kira apa yang saya temukan membingungkan adalah bahwa sepertinya memiliki array tipe nilai berukuran tetap seharusnya tidak sulit, dan itu akan memungkinkan banyak tipe data untuk disusun dengan referensi daripada dengan nilai. Sementara marshal-by-value dapat berguna dalam kasus-kasus yang benar-benar tidak dapat ditangani dengan cara lain, saya akan menganggap marshal-by-ref lebih unggul di hampir semua kasus di mana itu dapat digunakan, sehingga memungkinkan kasus tersebut untuk menyertakan struct dengan tetap Array -sized akan tampak fitur yang berguna.
supercat
BTW, situasi lain di mana parameter generik non-tipe akan berguna adalah dengan tipe data yang mewakili jumlah dimensi. Seseorang dapat memasukkan informasi dimensional dalam instance yang mewakili kuantitas, tetapi memiliki informasi seperti itu dalam suatu tipe akan memungkinkan seseorang untuk menentukan bahwa koleksi seharusnya menahan objek yang mewakili unit berdimensi tertentu.
supercat
35

Anders Hejlsberg sendiri menggambarkan perbedaan di sini " Generik dalam C #, Java, dan C ++ ".

jfs
sumber
Saya sangat suka wawancara itu. itu menjelaskan kepada orang-orang non-c # seperti saya apa yang terjadi dengan c # generics.
Johannes Schaub - litb
18

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.

Jörg W Mittag
sumber
3
LINQ terutama merupakan fitur perpustakaan (meskipun C # dan VB juga menambahkan gula sintaksis di sampingnya). Bahasa apa pun yang menargetkan 2.0 CLR dapat menggunakan LINQ sepenuhnya hanya dengan memuat rakitan System.Core.
Richard Berg
Ya, maaf, aku seharusnya lebih jelas. LINQ. Saya merujuk pada sintaks kueri, bukan operator kueri standar monadik, metode ekstensi LINQ atau antarmuka IQueryable. Jelas, Anda dapat menggunakannya dari bahasa .NET.
Jörg W Mittag
1
Saya sedang memikirkan opsi lain untuk Java. Bahkan Oracle tidak ingin merusak kompatibilitas ke belakang, mereka masih dapat membuat beberapa trik kompiler untuk menghindari tipe informasi yang terhapus. Misalnya, 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.
Earth Engine
14

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 amiliki. Untuk bahasa lain parsing akan langsung tetapi untuk C ++, cukup banyak evaluasi diperlukan sebelumnya.

Itu semakin buruk. Bagaimana jika my_int_typedidefinisikan 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_typeresolve ke int, oleh karena itu bharus jenis yang sama seperti a, kan?

Salah. Pada titik di mana kompiler mencoba menyelesaikan pernyataan ini, sebenarnya Y<int>::my_typebelum 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_typebukan 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.

Konrad Rudolph
sumber
2
Saya cukup setuju. Ada beberapa harapan. Sistem pelengkapan otomatis dan kompiler C ++ harus berinteraksi sangat erat. Saya cukup yakin Visual Studio tidak akan pernah memiliki fitur seperti itu, tetapi hal-hal bisa terjadi di Eclipse / CDT atau IDE lain berdasarkan GCC. HARAPAN! :)
Benoît
6

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.

serg10
sumber
Bahkan generik C # bukan hanya sihir kompiler, kompiler dapat melakukan sihir lebih lanjut untuk menghasilkan perpustakaan yang ada. Tidak ada alasan mengapa mereka perlu untuk mengubah nama ArrayListuntuk 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.
Earth Engine
4

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 #).

Daniel Earwicker
sumber
2
Lain 11 bulan terlambat ... Ada hal-hal yang dapat Anda lakukan dengan wildcard Java yang Anda tidak dapat dengan parameter tipe bernama. Anda dapat melakukan ini di Java: 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>.
R. Martinho Fernandes
1

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).

Di Freund
sumber
1

Di Jawa, generik hanya level kompiler, jadi Anda mendapatkan:

a = new ArrayList<String>()
a.getClass() => ArrayList

Perhatikan bahwa tipe 'a' adalah daftar array, bukan daftar string. Jadi jenis daftar pisang akan sama dengan () daftar monyet.

Boleh dikatakan.

izb
sumber
1

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.

di artikel Alex Miller tentang Java 7 Proposals

pek
sumber
0

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.

Domba
sumber
Itu bukan jenis kompatibilitas yang dibicarakan orang. Idenya adalah kompatibilitas mundur untuk runtime : Kode yang ditulis menggunakan generik di .NET 2.0 tidak dapat dijalankan pada versi yang lebih lama dari .NET framework / CLR. Demikian pula, jika Java akan memperkenalkan generik "benar", kode Java yang lebih baru tidak akan dapat berjalan pada JVM yang lebih lama (karena itu membutuhkan pemutusan perubahan pada bytecode).
tzaman
Itu .net, bukan generik. Selalu membutuhkan kompilasi ulang untuk menargetkan versi CLR tertentu. Ada kompatibilitas bytecode, ada kompatibilitas kode. Dan juga, saya menjawab secara khusus mengenai perlunya mengubah kode lama yang menggunakan Daftar lama untuk menggunakan Daftar obat generik baru, yang tidak benar sama sekali.
Sheepy
1
Saya pikir orang-orang berbicara tentang ke depan kesesuaian . Yaitu .net 2.0 kode untuk dijalankan pada. Net 1.1, yang akan rusak karena runtime 1.1 tidak tahu apa-apa tentang 2.0 "pseudo-class". Bukankah seharusnya "java tidak mengimplementasikan true generic karena mereka ingin mempertahankan kompatibilitas ke depan"? (Daripada mundur)
Sheepy 3-10
Masalah kompatibilitas halus. Saya tidak berpikir masalahnya adalah bahwa menambahkan generik "nyata" ke Jawa akan memengaruhi program apa pun yang menggunakan versi Jawa yang lebih lama, tetapi bahwa kode yang menggunakan generik "baru disempurnakan" akan mengalami kesulitan bertukar objek seperti itu dengan kode lama yang tidak tahu apa-apa tentang tipe baru. Misalkan, misalnya, sebuah program memiliki program ArrayList<Foo>yang ingin diteruskan ke metode yang lebih lama yang seharusnya diisi ArrayListdengan contoh Foo. Jika sebuah ArrayList<foo>bukan ArrayList, bagaimana cara membuatnya?
supercat