Perbedaan antara Daftar, Daftar <?>, Daftar <T>, Daftar <E>, dan Daftar <Object>


194

Apa perbedaan antara List, List<?>, List<T>, List<E>, dan List<Object>?

1. Daftar

List: adalah jenis mentah, oleh karena itu tidak typesafe. Itu hanya akan menghasilkan kesalahan runtime ketika casting buruk. Kami ingin kesalahan waktu kompilasi ketika para pemain buruk. Tidak disarankan untuk digunakan.

2. Daftar <?>

List<?>adalah wildcard tak terbatas. Tapi saya tidak yakin untuk apa ini? Saya dapat mencetak List<?>tanpa masalah:

public static void test(List<?> list){
    System.out.println(list);   // Works
}

Mengapa saya tidak bisa menambahkan item ke List<?>?

public static void test(List<?> list){
    list.add(new Long(2));     // Error
    list.add("2");             // Error
    System.out.println(list);
}

3. Daftar <T>

public static void test(List<T> list){   // T cannot be resolved
    System.out.println(list);
}

Saya tidak mengerti sintaks ini. Saya melihat sesuatu seperti ini, dan berhasil:

public <T> T[] toArray(T[] a){
    return a;   
}

Kadang-kadang, saya melihat <T>, atau <E>, atau <U>, <T,E>. Apakah mereka semua sama atau mereka mewakili sesuatu yang berbeda?

4. Daftar <Object>

Ini memberikan kesalahan "Metode test(List<Object>)ini tidak berlaku untuk argumen List<String>":

public static void test(List<Object> list){
    System.out.println(list);
}

Jika saya mencoba ini, maka saya mendapat "Tidak dapat mengirim dari List<String>ke List<Object>":

test((List<Object>) names);

Saya bingung. Stringadalah subkelas dari Object, jadi mengapa bukan List<String>subkelas dari List<Object>?

Jawaban:


77

1) Benar

2) Anda dapat menganggapnya sebagai daftar "hanya baca", di mana Anda tidak peduli dengan jenis barang tersebut. Misalnya, dapat digunakan dengan metode yang mengembalikan panjang daftar.

3) T, E dan U adalah sama, tetapi orang cenderung menggunakan misalnya T untuk tipe, E untuk Elemen, V untuk nilai dan K untuk kunci. Metode yang mengkompilasi mengatakan bahwa ia mengambil array dari tipe tertentu, dan mengembalikan array dengan tipe yang sama.

4) Anda tidak dapat mencampur jeruk dan apel. Anda bisa menambahkan Obyek ke daftar String Anda jika Anda bisa meneruskan daftar string ke metode yang mengharapkan daftar objek. (Dan tidak semua benda adalah string)


2
+1 untuk daftar hanya baca aktif 2. Saya menulis beberapa kode untuk menunjukkan ini di 2. tyvm
Thang Pham

Mengapa orang menggunakannya List<Object>?
Thang Pham

3
Ini adalah cara untuk membuat daftar yang menerima semua jenis barang, Anda jarang menggunakannya.
Kaj

Sebenarnya saya tidak akan berpikir siapa pun akan menggunakannya mengingat Anda tidak dapat memiliki pengenal sama sekali sekarang.
if_zero_equals_one

1
@jika_seluruh_equals_one Ya, tetapi Anda kemudian akan mendapatkan peringatan kompiler (itu akan memperingatkan dan mengatakan bahwa Anda menggunakan tipe mentah), dan Anda tidak pernah ingin mengkompilasi kode Anda dengan peringatan.
Kaj

26

Untuk bagian terakhir: Meskipun String adalah bagian dari Objek, tetapi Daftar <String> tidak diwarisi dari Daftar <Object>.


11
Poin yang sangat bagus; banyak yang berasumsi bahwa karena kelas C mewarisi dari kelas P, Daftar <C> itu juga mewarisi dari Daftar <P>. Seperti yang Anda tunjukkan, ini bukan masalahnya. Alasan mengapa adalah jika kita dapat membuang dari Daftar <String> ke Daftar <Object>, maka kita dapat memasukkan Objects ke dalam daftar itu, sehingga melanggar kontrak asli Daftar <String> ketika mencoba untuk mengambil suatu elemen.
Peter

2
+1. Poin yang bagus juga. Jadi mengapa orang menggunakannya List<Object>?
Thang Pham

9
Daftar <Object> dapat digunakan untuk menyimpan daftar objek dari berbagai kelas.
Farshid Zaker

20

Notasi List<?>berarti "daftar sesuatu (tapi saya tidak mengatakan apa)". Karena kode dalam testkarya untuk segala jenis objek dalam daftar, ini berfungsi sebagai parameter metode formal.

Menggunakan parameter type (seperti pada poin 3 Anda), mengharuskan parameter type dideklarasikan. Sintaks Java untuk itu adalah untuk meletakkan <T>di depan fungsi. Ini persis analog dengan mendeklarasikan nama parameter formal ke metode sebelum menggunakan nama-nama di tubuh metode.

Mengenai List<Object>tidak menerima a List<String>, itu masuk akal karena a Stringtidak Object; ini adalah subclass dari Object. Cara mengatasinya adalah mendeklarasikan public static void test(List<? extends Object> set) .... Tetapi kemudian extends Objectitu mubazir, karena setiap kelas meluas secara langsung atau tidak langsung Object.


Mengapa orang menggunakannya List<Object>?
Thang Pham

10
Saya pikir "daftar sesuatu" adalah makna yang lebih baik List<?>karena daftar tersebut adalah jenis tertentu tetapi tidak diketahui. List<Object>akan benar-benar menjadi "daftar apa saja" karena memang bisa berisi apa saja.
ColinD

1
@ColinD - Maksud saya "apa saja" dalam arti "satu hal". Tapi kamu benar; itu berarti, "daftar sesuatu, tetapi saya tidak akan memberi tahu Anda apa".
Ted Hopp

@ColinD itu maksudnya mengapa Anda mengulangi kata-katanya? ya itu ditulis dengan kata-kata yang sedikit berbeda, tetapi artinya sama ...
user25

14

Alasan Anda tidak dapat melemparkan List<String>untuk List<Object>adalah bahwa hal itu akan memungkinkan Anda untuk melanggar kendala dari List<String>.

Pikirkan tentang skenario berikut: Jika saya punya List<String>, itu seharusnya hanya berisi objek bertipe String. (Yang merupakan finalkelas)

Jika saya bisa melemparkan itu ke List<Object>, maka itu memungkinkan saya untuk menambah Objectdaftar itu, sehingga melanggar kontrak asli List<String>.

Jadi, secara umum, jika kelas Cmewarisi dari kelas P, Anda tidak bisa mengatakan itu GenericType<C>juga mewarisi dari GenericType<P>.

NB Saya sudah mengomentari ini dalam jawaban sebelumnya tetapi ingin mengembangkannya.


tyvm, saya mengunggah komentar dan jawaban Anda, karena itu adalah penjelasan yang sangat bagus. Sekarang di mana dan mengapa orang akan menggunakan List<Object>?
Thang Pham

3
Secara umum Anda tidak boleh menggunakan List<Object>karena itu semacam mengalahkan tujuan generik. Namun, ada beberapa kasus di mana kode lama mungkin Listmenerima jenis yang berbeda, jadi Anda mungkin ingin memperbaiki kode untuk menggunakan parameterisasi jenis hanya untuk menghindari peringatan kompiler untuk jenis mentah. (Tapi fungsinya tidak berubah)
Peter


5

Mari kita bicara tentang mereka dalam konteks sejarah Jawa;

  1. List:

Daftar berarti dapat memasukkan Obyek apa pun. Daftar itu dalam rilis sebelum Java 5.0; Java 5.0 memperkenalkan Daftar, untuk kompatibilitas ke belakang.

List list=new  ArrayList();
list.add(anyObject);
  1. List<?>:

?berarti Obyek yang tidak diketahui, bukan Objek apa pun; ?pengantar wildcard adalah untuk memecahkan masalah yang dibangun oleh Generic Type; lihat wildcard ; tetapi ini juga menyebabkan masalah lain:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
  1. List< T> List< E>

Berarti Deklarasi generik dengan premis tipe T atau E di Lib proyek Anda.

  1. List< Object> berarti parameterisasi generik.

5

Pada poin ketiga Anda, "T" tidak dapat diselesaikan karena tidak dideklarasikan, biasanya ketika Anda mendeklarasikan kelas generik Anda dapat menggunakan "T" sebagai nama parameter tipe terikat , banyak contoh online termasuk tutorial oracle yang menggunakan "T" sebagai nama parameter type, misalnya, Anda mendeklarasikan kelas seperti:

public class FooHandler<T>
{
   public void operateOnFoo(T foo) { /*some foo handling code here*/}

}

Anda mengatakan bahwa FooHandler's operateOnFoometode mengharapkan variabel tipe "T" yang dideklarasikan pada deklarasi kelas itu sendiri, dengan mengingat hal ini, Anda kemudian dapat menambahkan metode lain seperti

public void operateOnFoos(List<T> foos)

dalam semua kasus baik T, E atau U di sana semua pengidentifikasi parameter tipe, Anda bahkan dapat memiliki lebih dari satu tipe parameter yang menggunakan sintaksis

public class MyClass<Atype,AnotherType> {}

di ponint keempat Anda meskipun secara efektif Sting adalah sub jenis Objek, di kelas generik tidak ada hubungan seperti itu, List<String>bukan sub jenis List<Object>mereka adalah dua jenis yang berbeda dari sudut pandang kompiler, ini paling baik dijelaskan dalam entri blog ini


5

Teori

String[] dapat dilemparkan ke Object[]

tapi

List<String>tidak dapat dilemparkan ke List<Object>.

Praktek

Untuk daftar itu lebih halus dari itu, karena pada waktu kompilasi jenis parameter Daftar yang diteruskan ke metode tidak dicentang. Definisi metode mungkin juga mengatakan List<?>- dari sudut pandang kompiler itu setara. Inilah sebabnya mengapa contoh OP # 2 memberikan kesalahan runtime bukan mengkompilasi kesalahan.

Jika Anda menangani List<Object>parameter yang diteruskan ke metode dengan hati-hati sehingga Anda tidak memaksakan pemeriksaan tipe pada elemen apa pun dari daftar, maka Anda dapat menentukan metode Anda menggunakan List<Object>tetapi sebenarnya menerima List<String>parameter dari kode panggilan.

A. Jadi kode ini tidak akan memberikan kesalahan kompilasi atau runtime dan akan benar-benar (dan mungkin mengejutkan?):

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test(argsList);  // The object passed here is a List<String>
}

public static void test(List<Object> set) {
    List<Object> params = new ArrayList<>();  // This is a List<Object>
    params.addAll(set);       // Each String in set can be added to List<Object>
    params.add(new Long(2));  // A Long can be added to List<Object>
    System.out.println(params);
}

B. Kode ini akan memberikan kesalahan runtime:

public static void main(String[] args) {
    List argsList = new ArrayList<String>();
    argsList.addAll(Arrays.asList(args));
    test1(argsList);
    test2(argsList);
}

public static void test1(List<Object> set) {
    List<Object> params = set;  // Surprise!  Runtime error
}

public static void test2(List<Object> set) {
    set.add(new Long(2));       // Also a runtime error
}

C. Kode ini akan memberikan kesalahan runtime ( java.lang.ArrayStoreException: java.util.Collections$UnmodifiableRandomAccessList Object[]):

public static void main(String[] args) {
    test(args);
}

public static void test(Object[] set) {
    Object[] params = set;    // This is OK even at runtime
    params[0] = new Long(2);  // Surprise!  Runtime error
}

Dalam B, parameter setbukan yang diketik Listpada waktu kompilasi: kompilator melihatnya sebagai List<?>. Ada kesalahan runtime karena pada saat runtime, setmenjadi objek aktual yang dilewati main(), dan itu adalah a List<String>. A List<String>tidak bisa dilemparkan ke List<Object>.

Di C, parameter setmembutuhkan sebuah Object[]. Tidak ada kesalahan kompilasi dan tidak ada kesalahan runtime ketika dipanggil dengan String[]objek sebagai parameter. Itu karena String[]dilemparkan ke Object[]. Tetapi objek yang sebenarnya diterima oleh test()tetap a String[], itu tidak berubah. Jadi paramsobjek juga menjadi a String[]. Dan elemen 0 dari String[]tidak dapat ditugaskan ke Long!

(Mudah-mudahan saya memiliki semuanya di sini, jika alasan saya salah, saya yakin komunitas akan memberi tahu saya. DIPERBARUI: Saya telah memperbarui kode dalam contoh A sehingga benar-benar dikompilasi, sambil tetap menunjukkan poin yang dibuat.)


Saya sudah mencoba contoh A Anda itu itu tidak bekerja: List<Object> cannot be applied to List<String>. Anda tidak dapat meneruskan ArrayList<String>ke metode yang diharapkan ArrayList<Object>.
parsecer

Terima kasih, agak terlambat pada tanggal saya telah tweak contoh A sehingga sekarang berfungsi. Perubahan utama adalah mendefinisikan argsList secara umum di main ().
radfast

4

Masalah 2 OK, karena "System.out.println (set);" berarti "System.out.println (set.toString ());" set adalah turunan dari Daftar, jadi pengompil akan memanggil List.toString ();

public static void test(List<?> set){
set.add(new Long(2)); //--> Error  
set.add("2");    //--> Error
System.out.println(set);
} 
Element ? will not promise Long and String, so complier will  not accept Long and String Object

public static void test(List<String> set){
set.add(new Long(2)); //--> Error
set.add("2");    //--> Work
System.out.println(set);
}
Element String promise it a String, so complier will accept String Object

Masalah 3: simbol-simbol ini sama, tetapi Anda dapat memberikan spesifikasi yang berbeda. Sebagai contoh:

public <T extends Integer,E extends String> void p(T t, E e) {}

Masalah 4: Koleksi tidak memungkinkan kovarian parameter parameter. Tetapi array memungkinkan kovarians.


0

Anda benar: String adalah bagian dari Object. Karena String lebih "tepat" daripada Object, Anda harus membuatnya untuk menggunakannya sebagai argumen untuk System.out.println ().

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.