Kapan menggunakan metode umum dan kapan menggunakan wild-card?

122

Saya membaca tentang metode umum dari OracleDocGenericMethod . Saya cukup bingung tentang perbandingannya ketika dikatakan kapan harus menggunakan wild-card dan kapan harus menggunakan metode umum. Mengutip dari dokumen.

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

Kami dapat menggunakan metode umum di sini sebagai gantinya:

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!
}

[…] Ini memberitahu kita bahwa argumen type digunakan untuk polimorfisme; efek satu-satunya adalah mengizinkan berbagai jenis argumen aktual untuk digunakan di situs pemanggilan yang berbeda. Jika demikian, gunakan karakter pengganti. Karakter pengganti dirancang untuk mendukung subtipe fleksibel, yang kami coba ungkapkan di sini.

Bukankah menurut kami wild card (Collection<? extends E> c);juga mendukung jenis polimorfisme? Lalu mengapa penggunaan metode generik dianggap kurang baik dalam hal ini?

Melanjutkan ke depan, itu menyatakan,

Metode generik memungkinkan parameter tipe digunakan untuk mengekspresikan ketergantungan di antara tipe satu atau lebih argumen ke metode dan / atau tipe kembaliannya. Jika tidak ada ketergantungan seperti itu, metode umum tidak boleh digunakan.

Apa artinya ini?

Mereka telah mempresentasikan contohnya

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

[…]

Kita bisa saja menulis tanda tangan untuk metode ini dengan cara lain, tanpa menggunakan wildcard sama sekali:

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

Dokumen tersebut mencegah deklarasi kedua dan mempromosikan penggunaan sintaks pertama? Apa perbedaan antara deklarasi pertama dan kedua? Keduanya sepertinya melakukan hal yang sama?

Adakah yang bisa menjelaskan area ini.

benz
sumber

Jawaban:

173

Ada tempat-tempat tertentu, di mana karakter pengganti, dan parameter tipe melakukan hal yang sama. Tetapi ada juga tempat-tempat tertentu, di mana Anda harus menggunakan parameter tipe.

  1. Jika Anda ingin menerapkan beberapa hubungan pada tipe argumen metode yang berbeda, Anda tidak dapat melakukannya dengan wildcard, Anda harus menggunakan parameter tipe.

Mengambil metode Anda sebagai contoh, misalkan Anda ingin memastikan bahwa daftar srcdan yang destditeruskan ke copy()metode harus memiliki tipe parameter yang sama, Anda dapat melakukannya dengan parameter tipe seperti ini:

public static <T extends Number> void copy(List<T> dest, List<T> src)

Di sini, Anda dipastikan bahwa keduanya destdan srcmemiliki tipe parameter yang sama untuk List. Jadi, aman untuk menyalin elemen dari srcke dest.

Tapi, jika Anda terus mengubah metode untuk menggunakan wildcard:

public static void copy(List<? extends Number> dest, List<? extends Number> src)

itu tidak akan bekerja seperti yang diharapkan. Dalam kasus kedua, Anda dapat lulus List<Integer>dan List<Float>sebagai destdan src. Jadi, memindahkan elemen dari srcmenjadi desttidak akan menjadi tipe yang aman lagi. Jika Anda tidak membutuhkan relasi semacam itu, maka Anda bebas untuk tidak menggunakan parameter tipe sama sekali.

Beberapa perbedaan lain antara menggunakan karakter pengganti dan parameter tipe adalah:

  • Jika Anda hanya memiliki satu argumen tipe berparameter, Anda dapat menggunakan karakter pengganti, meskipun parameter tipe juga akan berfungsi.
  • Jenis parameter mendukung banyak batas, karakter pengganti tidak.
  • Karakter pengganti mendukung batas atas dan bawah, parameter tipe hanya mendukung batas atas. Jadi, jika Anda ingin mendefinisikan metode yang mengambil Listtipe Integeratau kelas supernya, Anda dapat melakukan:

    public void print(List<? super Integer> list)  // OK

    tetapi Anda tidak dapat menggunakan parameter tipe:

     public <T super Integer> void print(List<T> list)  // Won't compile

Referensi:

Rohit Jain
sumber
1
Ini jawaban yang aneh. Itu tidak menjelaskan mengapa Anda perlu menggunakan ?sama sekali. Anda dapat menulis ulang sebagai `public static <T1 extends Number, T2 extends Number> void copy (List <T1> dest, List <T2> src) dan dalam hal ini menjadi jelas apa yang sedang terjadi.
kan
@kan. Nah itulah masalah sebenarnya. Anda bisa menggunakan parameter type untuk menerapkan tipe yang sama, tetapi Anda tidak bisa melakukannya dengan wildcard. Menggunakan dua tipe yang berbeda untuk parameter tipe adalah hal yang berbeda.
Rohit Jain
1
@benz. Anda tidak dapat menentukan batas bawah dalam Listmenggunakan parameter type. List<T super Integer>tidak valid, dan tidak akan dikompilasi.
Rohit Jain
2
@benz. Sama-sama :) Saya sangat menyarankan Anda untuk membaca tautan yang saya posting di bagian akhir. Itu adalah sumber daya terbaik tentang Generik yang akan Anda dapatkan.
Rohit Jain
3
@ jorgen.ringen <T extends X & Y>-> banyak batas.
Rohit Jain
12

Pertimbangkan contoh berikut dari The Java Programming oleh James Gosling edisi ke-4 di bawah ini di mana kami ingin menggabungkan 2 SinglyLinkQueue:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

Kedua metode di atas memiliki fungsi yang sama. Jadi mana yang lebih disukai? Jawabannya adalah yang kedua. Dengan kata-kata penulis sendiri:

"Aturan umumnya adalah menggunakan karakter pengganti jika Anda bisa karena kode dengan karakter pengganti umumnya lebih mudah dibaca daripada kode dengan beberapa parameter tipe. Saat memutuskan apakah Anda memerlukan variabel tipe, tanyakan pada diri Anda apakah variabel tipe itu digunakan untuk menghubungkan dua atau lebih parameter, atau untuk menghubungkan tipe parameter dengan tipe kembalian. Jika jawabannya tidak, maka karakter pengganti harus mencukupi. "

Catatan: Dalam buku hanya metode kedua yang diberikan dan nama parameter tipe adalah S, bukan 'T'. Metode pertama tidak ada di buku.

chammu
sumber
saya memilih kutipan sebuah buku, itu langsung dan ringkas
Kurapika
9

Dalam pertanyaan pertama Anda: Ini berarti bahwa jika ada hubungan antara tipe parameter dan tipe kembalian metode, maka gunakan generik.

Sebagai contoh:

public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);

Di sini Anda mengekstrak beberapa T mengikuti kriteria tertentu. Jika T Longmetode Anda akan kembali Longdan Collection<Long>; tipe kembalian aktual bergantung pada tipe parameter, oleh karena itu berguna, dan disarankan, untuk menggunakan tipe generik.

Jika tidak demikian, Anda dapat menggunakan jenis kartu liar:

public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);

Dalam dua contoh ini, apa pun tipe item dalam koleksi, tipe pengembaliannya adalah intdan boolean.

Dalam contoh Anda:

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

kedua fungsi tersebut akan mengembalikan boolean apa pun jenis item dalam koleksi. Dalam kasus kedua, ini terbatas pada instance dari subclass E.

Pertanyaan kedua:

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

Kode pertama ini memungkinkan Anda untuk mengirimkan heterogen List<? extends T> srcsebagai parameter. Daftar ini dapat berisi banyak elemen dari kelas yang berbeda selama semuanya memperluas kelas dasar T.

jika Anda memiliki:

interface Fruit{}

dan

class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}

Anda bisa melakukannya

List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>(); 

Collections.copy(fridge, basket);// works 

Di samping itu

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

membatasi List<S> srcmenjadi satu kelas tertentu S yang merupakan subkelas dari T. Daftar hanya dapat berisi elemen dari satu kelas (dalam contoh ini S) dan tidak ada kelas lain, bahkan jika mereka mengimplementasikan T juga. Anda tidak akan dapat menggunakan contoh saya sebelumnya tetapi Anda dapat melakukan:

List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();

Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
le-doude
sumber
1
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();Bukan sintaks yang valid. Anda harus membuat instance ArrayList tanpa batas.
Arnold Pistorius
Tidak dapat menambahkan Apple ke keranjang dalam contoh di atas karena keranjang itu bisa berupa daftar Buah Pir. Contoh AFAIK salah. Dan tidak bisa dikompilasi juga.
Khanna111
1
@ArnoldPistusus Itu membuatku bingung. Saya memeriksa dokumentasi API ArrayList dan memiliki konstruktor yang ditandatangani ArrayList(Collection<? extends E> c). Bisakah Anda menjelaskan mengapa Anda mengatakan itu?
Kurapika
@Kurapika Mungkinkah saya menggunakan Java versi lama? Komentar telah diposting hampir 3 tahun yang lalu.
Arnold Pistorius
2

Metode wildcard juga umum - Anda bisa menyebutnya dengan beberapa tipe.

The <T>sintaks mendefinisikan nama tipe variabel. Jika variabel tipe memiliki kegunaan apa pun (misalnya dalam implementasi metode atau sebagai batasan untuk tipe lain), maka masuk akal untuk menamainya, jika tidak, Anda dapat menggunakan ?, sebagai variabel anonim. Jadi, sepertinya hanya jalan pintas.

Selain itu, ?sintaksis tidak dapat dihindari saat Anda mendeklarasikan bidang:

class NumberContainer
{
 Set<? extends Number> numbers;
}
kan
sumber
3
Bukankah ini seharusnya menjadi komentar?
Buhake Sindi
@BuhakeSindi Maaf, apa yang tidak jelas? Mengapa -1? Saya pikir itu menjawab pertanyaan itu.
kan
2

Saya akan mencoba menjawab pertanyaan Anda, satu per satu.

Bukankah menurut kami wild card (Collection<? extends E> c);juga mendukung jenis polimorfisme?

Tidak. Alasannya adalah karakter pengganti terikat tidak memiliki jenis parameter yang ditentukan. Itu tidak diketahui. Semua yang "diketahui" adalah bahwa "penahanan" adalah tipe E(apa pun yang didefinisikan). Jadi, ini tidak dapat memverifikasi dan membenarkan apakah nilai yang diberikan cocok dengan tipe yang dibatasi.

Jadi, tidak masuk akal untuk memiliki perilaku polimorfik pada karakter pengganti.

Dokumen tersebut mencegah deklarasi kedua dan mempromosikan penggunaan sintaks pertama? Apa perbedaan antara deklarasi pertama dan kedua? Keduanya sepertinya melakukan hal yang sama?

Pilihan pertama lebih baik dalam kasus ini karena Tselalu dibatasi, dan sourcepasti akan memiliki nilai (tidak diketahui) yang subclass T.

Jadi, misalkan Anda ingin menyalin semua daftar nomor, opsi pertama adalah

Collections.copy(List<Number> dest, List<? extends Number> src);

src, pada dasarnya, dapat menerima List<Double>,, List<Float>dll. karena ada batas atas tipe parameter yang ditemukan di dest.

Opsi kedua akan memaksa Anda untuk mengikat Suntuk setiap jenis yang ingin Anda salin, seperti itu

//For double 
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.

//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.

As Sadalah tipe berparameter yang membutuhkan pengikatan.

Saya harap ini membantu.

Buhake Sindi
sumber
Bisakah Anda menjelaskan apa yang Anda maksud dalam paragraf terakhir Anda
benz
Yang menyatakan opsi ke-2 akan memaksa Anda untuk mengikat salah satu ... dapatkah Anda menjelaskannya lebih lanjut
benz
<S extends T>menyatakan bahwa Sadalah tipe berparameter yang merupakan subkelas dari T, sehingga memerlukan tipe berparameter (tanpa karakter pengganti) yang merupakan subkelas T.
Buhake Sindi
2

Satu perbedaan lain yang tidak tercantum di sini.

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // correct
    }
}

Tetapi hal berikut akan mengakibatkan kesalahan waktu kompilasi.

static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
    for (T o : a) {
        c.add(o); // compile time error
    }
}
Vivek Kumar
sumber
0

Sejauh yang saya mengerti, hanya ada satu kasus penggunaan ketika wildcard sangat dibutuhkan (yaitu dapat mengekspresikan sesuatu yang tidak dapat Anda ungkapkan menggunakan parameter tipe eksplisit). Ini adalah saat Anda perlu menentukan batas bawah.

Selain itu, karakter pengganti berfungsi untuk menulis kode yang lebih ringkas, seperti yang dijelaskan oleh pernyataan berikut dalam dokumen yang Anda sebutkan:

Metode generik memungkinkan parameter tipe digunakan untuk mengekspresikan ketergantungan di antara tipe satu atau lebih argumen ke metode dan / atau tipe kembaliannya. Jika tidak ada ketergantungan seperti itu, metode umum tidak boleh digunakan.

[...]

Penggunaan karakter pengganti lebih jelas dan lebih ringkas daripada mendeklarasikan parameter tipe eksplisit, dan oleh karena itu sebaiknya lebih disukai jika memungkinkan.

[...]

Karakter pengganti juga memiliki keuntungan karena dapat digunakan di luar tanda tangan metode, seperti jenis bidang, variabel lokal, dan array.

Martin Maletinsky
sumber
0

Terutama -> Wildcard memberlakukan generik pada level parameter / argumen dari metode Non-Generik. Catatan. Ini juga dapat dilakukan di genericMethod secara default, tetapi di sini, bukan? kita bisa menggunakan T itu sendiri.

paket obat generik;

public class DemoWildCard {


    public static void main(String[] args) {
        DemoWildCard obj = new DemoWildCard();

        obj.display(new Person<Integer>());
        obj.display(new Person<String>());

    }

    void display(Person<?> person) {
        //allows person of Integer,String or anything
        //This cannnot be done if we use T, because in that case we have to make this method itself generic
        System.out.println(person);
    }

}

class Person<T>{

}

SO wildcard memiliki kegunaan khusus seperti ini.

Arasn
sumber