Mengapa tidak sah memiliki dua metode berikut di kelas yang sama?
class Test{
void add(Set<Integer> ii){}
void add(Set<String> ss){}
}
Saya mendapatkan compilation error
Metode add (Set) memiliki add erasure yang sama (Set) seperti metode lain dalam tipe Test.
sementara saya bisa mengatasinya, saya bertanya-tanya mengapa javac tidak suka ini.
Saya dapat melihat bahwa dalam banyak kasus, logika kedua metode tersebut akan sangat mirip dan dapat digantikan oleh satu
public void add(Set<?> set){}
metode, tetapi ini tidak selalu terjadi.
Ini sangat menjengkelkan jika Anda ingin memiliki dua constructors
argumen seperti itu karena Anda tidak dapat mengubah nama salah satu saja constructors
.
List
dan saya tidak tahu bagaimana menanganinya.Jawaban:
Aturan ini dimaksudkan untuk menghindari konflik dalam kode lawas yang masih menggunakan tipe mentah.
Berikut ilustrasi mengapa ini tidak diizinkan, diambil dari JLS. Misalkan, sebelum obat generik diperkenalkan ke Java, saya menulis beberapa kode seperti ini:
Anda memperpanjang kelas saya, seperti ini:
Setelah pengenalan obat generik, saya memutuskan untuk memperbarui perpustakaan saya.
Anda tidak siap untuk membuat pembaruan apa pun, sehingga Anda meninggalkan
Overrider
kelas Anda sendirian. Untuk menggantitoList()
metode ini dengan benar , perancang bahasa memutuskan bahwa jenis mentah "override-equivalen" untuk semua jenis yang dibuat. Ini berarti bahwa meskipun tanda tangan metode Anda tidak lagi secara resmi sama dengan tanda tangan superclass saya, metode Anda masih menimpanya.Sekarang, waktu berlalu dan Anda memutuskan siap memperbarui kelas Anda. Tetapi Anda sedikit mengacaukan, dan alih-alih mengedit
toList()
metode mentah yang ada, Anda menambahkan metode baru seperti ini:Karena mengesampingkan ekuivalensi jenis mentah, kedua metode berada dalam bentuk yang valid untuk mengganti
toList(Collection<T>)
metode. Tetapi tentu saja, kompiler perlu menyelesaikan satu metode. Untuk menghilangkan ambiguitas ini, kelas tidak diperbolehkan memiliki beberapa metode yang override-equivalen — yaitu, beberapa metode dengan tipe parameter yang sama setelah penghapusan.Kuncinya adalah bahwa ini adalah aturan bahasa yang dirancang untuk menjaga kompatibilitas dengan kode lama menggunakan tipe mentah. Ini bukan batasan yang diperlukan oleh penghapusan parameter tipe; karena resolusi metode terjadi pada waktu kompilasi, menambahkan tipe generik ke pengidentifikasi metode sudah cukup.
sumber
javac
.List<?>
dan beberapaenum
yang Anda tentukan untuk menunjukkan tipe. Jenis-logika spesifik sebenarnya bisa dalam metode pada enum. Sebagai alternatif, Anda mungkin ingin membuat dua kelas yang berbeda, bukan satu kelas dengan dua konstruktor yang berbeda. Logika umum adalah dalam superclass atau objek helper yang didelegasikan kedua tipe.Java generics menggunakan tipe erasure. Bit dalam kurung sudut (
<Integer>
dan<String>
) akan dihapus, sehingga Anda akan berakhir dengan dua metode yang memiliki tanda tangan yang identik (yangadd(Set)
Anda lihat dalam kesalahan). Itu tidak diizinkan karena runtime tidak akan tahu mana yang harus digunakan untuk setiap kasus.Jika Java pernah mendapat reified generics, maka Anda bisa melakukan ini, tapi itu mungkin tidak mungkin sekarang.
sumber
Ini karena Java Generics diimplementasikan dengan Type Erasure .
Metode Anda akan diterjemahkan, pada waktu kompilasi, ke sesuatu seperti:Resolusi metode terjadi pada waktu kompilasi dan tidak mempertimbangkan parameter tipe. ( lihat jawaban erickson )
Kedua metode memiliki tanda tangan yang sama tanpa parameter tipe, karenanya kesalahan.
sumber
Masalahnya adalah itu
Set<Integer>
danSet<String>
sebenarnya diperlakukan sebagaiSet
dari JVM. Memilih jenis untuk Set (String atau Integer dalam kasus Anda) hanya gula sintaksis yang digunakan oleh kompiler. JVM tidak dapat membedakan antaraSet<String>
danSet<Integer>
.sumber
Set
, tetapi karena resolusi metode terjadi pada waktu kompilasi, ketika informasi yang diperlukan tersedia, ini tidak relevan. Masalahnya adalah bahwa membiarkan kelebihan ini akan bertentangan dengan penyisihan untuk jenis mentah, sehingga mereka dibuat ilegal dalam sintaksis Java.(Ljava/util/Collection;)Ljava/util/List;
tidak berfungsi. Anda dapat menggunakan(Ljava/util/Collection<String>;)Ljava/util/List<String>;
, tetapi itu adalah perubahan yang tidak kompatibel dan Anda akan mengalami masalah yang tidak dapat diselesaikan di tempat-tempat yang Anda miliki adalah tipe yang terhapus. Anda mungkin harus menghapus sepenuhnya, tetapi itu cukup rumit.Tentukan satu Metode tanpa tipe suka
void add(Set ii){}
Anda dapat menyebutkan jenisnya saat memanggil metode berdasarkan pilihan Anda. Ini akan bekerja untuk semua jenis set.
sumber
Bisa jadi kompiler menerjemahkan Set (Integer) ke Set (Object) dalam kode byte java. Jika demikian, Set (Integer) hanya akan digunakan pada tahap kompilasi untuk memeriksa sintaks.
sumber
Set
. Generik tidak ada dalam kode byte, mereka sintaksis gula untuk casting dan memberikan keamanan tipe waktu kompilasi.Saya menabrak ini ketika mencoba menulis sesuatu seperti:
Continuable<T> callAsync(Callable<T> code) {....}
danContinuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
Mereka menjadi untuk kompiler 2 definisiContinuable<> callAsync(Callable<> veryAsyncCode) {...}
Penghapusan tipe secara harfiah berarti menghapus informasi argumen tipe dari obat generik. Ini SANGAT menjengkelkan, tetapi ini adalah batasan yang akan ada dengan Java untuk sementara waktu. Untuk kasus konstruktor tidak banyak yang bisa dilakukan, 2 subclass baru khusus dengan parameter yang berbeda dalam konstruktor misalnya. Atau gunakan metode inisialisasi sebagai gantinya ... (konstruktor virtual?) Dengan nama yang berbeda ...
untuk metode operasi yang sama penggantian nama akan membantu, seperti
Atau dengan beberapa nama yang lebih deskriptif, mendokumentasikan diri sendiri untuk kasus oyu, suka
addNames
danaddIndexes
atau semacamnya.sumber