Apa perbedaan antara 'E', 'T', dan '?' untuk generik Java?

261

Saya menemukan kode Java seperti ini:

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

Apa perbedaan di antara ketiga hal di atas dan apa yang mereka sebut jenis deklarasi kelas atau antarmuka di Jawa?

kartu as
sumber
1
Saya ragu ada perbedaan. Saya kira itu hanya nama untuk parameter tipe itu. Dan apakah yang terakhir bahkan valid?
CodesInChaos

Jawaban:

231

Yah tidak ada perbedaan antara dua yang pertama - mereka hanya menggunakan nama yang berbeda untuk parameter tipe ( Eatau T).

Yang ketiga bukan deklarasi yang valid - ?digunakan sebagai wildcard yang digunakan saat memberikan argumen tipe , mis. List<?> foo = ...Sarana yang foomerujuk ke daftar jenis tertentu, tapi kami tidak tahu apa.

Semua ini adalah obat generik , yang merupakan topik yang cukup besar. Anda mungkin ingin mempelajarinya melalui sumber daya berikut, walaupun tentu saja ada lebih banyak tersedia:

Jon Skeet
sumber
1
Sepertinya tautan ke PDF rusak. Saya telah menemukan apa yang tampak sebagai salinan di sini , tetapi saya tidak dapat memastikan 100% karena saya tidak tahu seperti apa aslinya.
John
2
@ John: Yup, itu dia. Akan mengedit tautan, apakah yang itu atau yang Oracle ...
Jon Skeet
Apakah ada selain T, E dan? digunakan dalam obat generik? Jika demikian, apakah mereka dan apa artinya?
sofs1
1
@ sofs1: Tidak ada yang istimewa Tdan E- mereka hanya pengidentifikasi. Anda bisa menulis KeyValuePair<K, V>misalnya. ?memiliki makna khusus.
Jon Skeet
215

Itu lebih banyak konvensi daripada yang lainnya.

  • T dimaksudkan untuk menjadi Tipe
  • Edimaksudkan untuk menjadi Elemen ( List<E>: daftar Elemen)
  • Kadalah Kunci (dalam a Map<K,V>)
  • V adalah Nilai (sebagai nilai balik atau nilai yang dipetakan)

Mereka sepenuhnya dipertukarkan (konflik dalam deklarasi yang sama meskipun).

orang aneh
sumber
20
Huruf antara <> hanyalah sebuah nama. Apa yang Anda gambarkan dalam jawaban Anda hanyalah konvensi. Itu bahkan tidak harus menjadi huruf tunggal, huruf besar; Anda dapat menggunakan nama apa pun yang Anda suka, sama seperti Anda bisa memberikan kelas, variabel dll nama apa pun yang Anda suka.
Jesper
Deskripsi yang lebih terperinci dan jelas tersedia di artikel ini oracle.com/technetwork/articles/java/…
fgul
6
Anda tidak menjelaskan pada tanda tanya. Diturunkan.
shinzou
129

Jawaban sebelumnya menjelaskan parameter tipe (T, E, dll.), Tetapi jangan menjelaskan wildcard, "?", Atau perbedaan di antara mereka, jadi saya akan membahasnya.

Pertama, untuk memperjelas: wildcard dan parameter type tidak sama. Di mana parameter tipe mendefinisikan semacam variabel (misalnya, T) yang mewakili tipe untuk cakupan, wildcard tidak: wildcard hanya mendefinisikan sekumpulan tipe yang diijinkan yang dapat Anda gunakan untuk tipe generik. Tanpa ada pembatas ( extendsatau super), wildcard berarti "gunakan jenis apa pun di sini".

Wildcard selalu datang di antara kurung sudut, dan itu hanya memiliki makna dalam konteks tipe generik:

public void foo(List<?> listOfAnyType) {...}  // pass a List of any type

tidak pernah

public <?> ? bar(? someType) {...}  // error. Must use type params here

atau

public class MyGeneric ? {      // error
    public ? getFoo() { ... }   // error
    ...
}

Semakin membingungkan di mana mereka tumpang tindih. Sebagai contoh:

List<T> fooList;  // A list which will be of type T, when T is chosen.
                  // Requires T was defined above in this scope
List<?> barList;  // A list of some type, decided elsewhere. You can do
                  // this anywhere, no T required.

Ada banyak tumpang tindih dalam apa yang mungkin dengan definisi metode. Berikut ini adalah, secara fungsional, identik:

public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething)  {...}

Jadi, jika ada tumpang tindih, mengapa menggunakan yang satu atau yang lain? Kadang-kadang, itu hanya gaya: beberapa orang mengatakan bahwa jika Anda tidak memerlukan param tipe, Anda harus menggunakan wildcard hanya untuk membuat kode lebih sederhana / lebih mudah dibaca. Satu perbedaan utama yang saya jelaskan di atas: tipe params mendefinisikan variabel tipe (misalnya, T) yang dapat Anda gunakan di tempat lain dalam ruang lingkup; wildcard tidak. Kalau tidak, ada dua perbedaan besar antara tipe params dan wildcard:

Tipe params dapat memiliki beberapa kelas pembatas; wildcard tidak dapat:

public class Foo <T extends Comparable<T> & Cloneable> {...}

Wildcard dapat memiliki batas bawah; jenis params tidak dapat:

public void bar(List<? super Integer> list) {...}

Di atas List<? super Integer>didefinisikan Integersebagai batas bawah pada wildcard, artinya tipe Daftar harus Integer atau tipe super Integer. Pembatasan tipe generik melampaui apa yang ingin saya bahas secara rinci. Singkatnya, ini memungkinkan Anda untuk menentukan jenis tipe generik. Hal ini memungkinkan untuk mengobati obat generik secara polimorfik. Misalnya dengan:

public void foo(List<? extends Number> numbers) {...}

Anda dapat lulus List<Integer>, List<Float>, List<Byte>, dll untuk numbers. Tanpa batasan jenis, ini tidak akan berhasil - itu hanya bagaimana generik.

Akhirnya, inilah definisi metode yang menggunakan wildcard untuk melakukan sesuatu yang saya pikir Anda tidak bisa melakukan cara lain:

public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
    numberSuper.add(elem);
}

numberSuperdapat berupa Daftar Nomor atau supertipe Nomor (misalnya, List<Object>), dan elemharus Nomor atau subtipe apa pun. Dengan semua lompatan, kompiler dapat yakin bahwa itu .add()adalah typesafe.

Hawkeye Parker
sumber
"public void foo (Daftar nomor <? extends>>) {...}" haruskah "extends" menjadi "super"?
1a1a11a
1
Maksud dari contoh itu adalah untuk menunjukkan tanda tangan yang secara polimorfis mendukung Daftar Nomor dan sub-jenis Nomor. Untuk ini, Anda menggunakan "extends". Yaitu, "berikan saya Daftar Angka atau apa pun yang meluas Angka" (Daftar <Integer>, Daftar <Float>, apa pun). Metode seperti ini mungkin kemudian beralih melalui daftar dan, untuk setiap elemen, "e", jalankan, misalnya, e.floatValue (). Tidak masalah apa pun sub-tipe (ekstensi) Angka yang Anda lewati - Anda akan selalu dapat ".FloatValue ()", karena .floatValue () adalah metode Number.
Hawkeye Parker
Pada contoh terakhir Anda, "Daftar <? Nomor super>" hanya bisa menjadi "Daftar <Number>" karena metode ini tidak memungkinkan hal lain yang lebih umum.
jessarah
@ jessarah nggak. Mungkin contoh saya tidak jelas, tetapi saya menyebutkan dalam contoh bahwa adder () dapat mengambil Daftar <Object> (Objek adalah superclass of Number). Jika Anda ingin dapat melakukan ini, ia harus memiliki tanda tangan "Daftar <? Nomor super>". Itulah titik "super" di sini.
Hawkeye Parker
2
Jawaban ini sangat baik dalam menjelaskan perbedaan antara wildcard dan parameter tipe, harus ada satu pertanyaan khusus dengan jawaban ini. Saya semakin mendalam tentang obat-obatan generik akhir-akhir ini dan jawaban ini banyak membantu saya dalam menyatukan berbagai hal, banyak informasi singkat, terima kasih!
Testo Testini
27

Variabel tipe, <T>, bisa berupa tipe non-primitif apa pun yang Anda tentukan: tipe kelas apa pun, tipe antarmuka apa pun, tipe array apa pun, atau bahkan variabel tipe lain.

Nama parameter tipe yang paling umum digunakan adalah:

  • E-Element (digunakan secara luas oleh Java Collections Framework)
  • K - Key
  • N - Nomor
  • Tipe T
  • V - Nilai

Di Java 7 diizinkan untuk instantiate seperti ini:

Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Uva
sumber
3

Nama parameter tipe yang paling umum digunakan adalah:

E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

Anda akan melihat nama-nama ini digunakan di seluruh Java SE API

Waqas Ahmed
sumber
2

kompiler akan membuat tangkapan untuk setiap wildcard (misalnya, tanda tanya di Daftar) ketika membuat fungsi seperti:

foo(List<?> list) {
    list.put(list.get()) // ERROR: capture and Object are not identical type.
}

Namun tipe generik seperti V akan baik-baik saja dan menjadikannya metode generik :

<V>void foo(List<V> list) {
    list.put(list.get())
}
Tiina
sumber