Apa perbedaan antara <? extends Base> dan <T extends Base>?

29

Dalam contoh ini:

import java.util.*;

public class Example {
    static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    static void function(List<? extends Number> outer)
    {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

doesntCompile() gagal dikompilasi dengan:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

sementara compiles()diterima oleh kompiler.

Jawaban ini menjelaskan bahwa satu-satunya perbedaan adalah tidak seperti itu <? ...>, <T ...>memungkinkan Anda merujuk jenisnya nanti, yang tampaknya tidak menjadi masalah.

Apa perbedaan antara <? extends Number>dan <T extends Number>dalam kasus ini dan mengapa tidak dikompilasi pertama?

Dev Null
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Samuel Liew
[1] Java Generic type: perbedaan antara Daftar <? extends Number> dan List <T extends Number> tampaknya menanyakan pertanyaan yang sama, tetapi meskipun mungkin menarik, itu bukan duplikat. [2] Meskipun ini adalah pertanyaan yang bagus, judulnya tidak mencerminkan pertanyaan spesifik yang diajukan dalam kalimat terakhir.
skomisa
Ini sudah dijawab di sini Daftar Generik Java <Daftar <? extends Number >>
M Qunh Quyết Nguyễn
Bagaimana dengan penjelasannya di sini ?
jrook

Jawaban:

14

Dengan mendefinisikan metode dengan tanda tangan berikut:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

dan memohon seperti:

compiles(new HashMap<Integer, List<Integer>>());

Dalam jls §8.1.2 kita temukan, itu (bagian yang menarik dicetak tebal oleh saya):

Deklarasi kelas generik mendefinisikan sekumpulan tipe parameter (§4.5), satu untuk setiap kemungkinan doa bagian parameter tipe berdasarkan argumen tipe . Semua tipe parameter ini berbagi kelas yang sama pada saat run time.

Dengan kata lain, tipe Tini cocok dengan tipe input dan ditetapkan Integer. Tanda tangan akan efektif menjadi static void compiles(Map<Integer, List<Integer>> map).

Ketika sampai pada doesntCompilemetode, jls mendefinisikan aturan subtyping ( §4.5.1 , dicetak tebal oleh saya):

Argumen tipe T1 dikatakan mengandung argumen tipe T2 lain, ditulis T2 <= T1, jika himpunan tipe yang dilambangkan dengan T2 terbukti merupakan himpunan bagian dari himpunan tipe yang ditunjukkan oleh T1 di bawah penutupan reflektif dan transitif dari aturan berikut ( di mana <: menunjukkan subtyping (§4.10)):

  • ? memperpanjang T <=? memanjang S jika T <: S

  • ? memperpanjang T <=?

  • ? super T <=? super S jika S <: T

  • ? super T <=?

  • ? super T <=? memperpanjang Object

  • T <= T

  • T <=? memanjang T

  • T <=? super T

Ini berarti, yang ? extends Numbermemang mengandung Integeratau bahkan List<? extends Number>mengandung List<Integer>, tetapi tidak demikian halnya dengan Map<Integer, List<? extends Number>>dan Map<Integer, List<Integer>>. Lebih lanjut tentang topik itu dapat ditemukan di utas SO ini . Anda masih bisa membuat versi dengan ?wildcard berfungsi dengan mendeklarasikan, bahwa Anda mengharapkan subtipe dari List<? extends Number>:

public class Example {
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}
Andronicus
sumber
[1] Menurut saya maksud Anda ? extends Numberbukan ? extends Numeric. [2] Pernyataan Anda bahwa "bukan itu yang terjadi pada Daftar <? Extends Number> dan List <Integer>" tidak benar. Seperti yang telah ditunjukkan oleh @VinceEmigh, Anda dapat membuat metode static void demo(List<? extends Number> lst) { }dan menyebutnya seperti ini demo(new ArrayList<Integer>());atau ini demo(new ArrayList<Float>());, dan kode tersebut mengkompilasi dan menjalankan OK. Atau mungkin saya salah membaca atau salah mengerti apa yang Anda katakan?
skomisa
@ Anda benar dalam kedua kasus. Ketika sampai pada poin kedua Anda, saya menulisnya dengan cara yang menyesatkan. Maksud saya List<? extends Number>sebagai parameter tipe seluruh peta, bukan dirinya sendiri. Terima kasih banyak atas komentarnya.
Andronicus
@skomisa dari alasan yang sama List<Number>tidak mengandung List<Integer>. Misalkan Anda memiliki fungsi static void check(List<Number> numbers) {}. Ketika memohon check(new ArrayList<Integer>());padanya tidak dikompilasi, Anda harus mendefinisikan metode sebagai static void check(List<? extends Number> numbers) {}. Dengan peta itu sama tetapi dengan lebih banyak bersarang.
Andronicus
1
@skomisa sama seperti Numberjenis parameter daftar dan Anda perlu menambahkan ? extendsuntuk membuatnya kovarian, List<? extends Number>adalah tipe parameter Mapdan juga perlu ? extendskovarians.
Andronicus
1
BAIK. Karena Anda memberikan solusi untuk wildcard multi level (alias "wildcard bersarang" ?), Dan ditautkan ke referensi JLS yang relevan, dapatkan hadiahnya.
skomisa
6

Dalam panggilan:

compiles(new HashMap<Integer, List<Integer>>());

T cocok dengan Integer, jadi jenis argumennya adalah a Map<Integer,List<Integer>>. Ini bukan kasus untuk metode doesntCompile: jenis argumen tetap Map<Integer, List<? extends Number>>apa pun argumen aktual dalam panggilan; dan itu tidak dapat ditugaskan dari HashMap<Integer, List<Integer>>.

MEMPERBARUI

Dalam doesntCompilemetode ini, tidak ada yang mencegah Anda melakukan hal seperti ini:

static void doesntCompile(Map<Integer, List<? extends Number>> map) {
    map.put(1, new ArrayList<Double>());
}

Jadi jelas, itu tidak dapat menerima HashMap<Integer, List<Integer>>argumen.

Maurice Perry
sumber
Seperti apa panggilan yang sah doesntCompileitu? Hanya ingin tahu tentang itu.
Xtreme Biker
1
@ XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());akan berfungsi, seperti biasa doesntCompile(new HashMap<>());.
skomisa
@XtremeBiker, bahkan ini akan berfungsi, Peta <Integer, List <? extends Number >> map = HashMap baru <Integer, List <? memperpanjang Number >> (); map.put (null, ArrayList <Integer> ()) baru; doesntCompile (peta);
MOnkey
"itu tidak dapat dialihkan dari HashMap<Integer, List<Integer>>" bisakah Anda menjelaskan mengapa itu tidak dapat dialihkan dari itu?
Dev Null
@ DevNull lihat pembaruan saya di atas
Maurice Perry
2

Contoh demonstrasi yang disederhanakan. Contoh yang sama dapat divisualisasikan seperti di bawah ini.

static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}

List<Pair<? extends Number>>adalah tipe wildcard multi-level sedangkan List<? extends Number>tipe wildcard standar.

Instansiasi konkret yang valid dari tipe wild card List<? extends Number>termasuk Numberdan setiap subtipe dari Numbersedangkan dalam kasus List<Pair<? extends Number>>yang merupakan argumen tipe dari argumen tipe dan itu sendiri memiliki instantiasi konkret dari tipe generik.

Generik tidak tetap sehingga Pair<? extends Number>tipe wild card hanya dapat menerima Pair<? extends Number>>. Tipe dalam ? extends Numbersudah kovarian. Anda harus menjadikan tipe penutup sebagai kovarian untuk memungkinkan kovarian.

Sagar Veeram
sumber
Bagaimana itu <Pair<Integer>>tidak bekerja dengan <Pair<? extends Number>>tetapi bekerja dengan <T extends Number> <Pair<T>>?
jaco0646
@ jaco0646 Anda pada dasarnya mengajukan pertanyaan yang sama dengan OP, dan jawaban dari Andronicus telah diterima. Lihat contoh kode dalam jawaban itu.
skomisa
@skomisa, ya, saya mengajukan pertanyaan yang sama karena beberapa alasan: satu adalah bahwa jawaban ini tampaknya tidak benar-benar menjawab pertanyaan OP; tetapi dua adalah bahwa saya menemukan jawaban ini lebih mudah untuk dipahami. Aku tidak bisa mengikuti jawaban dari Andronicus dengan cara apapun yang mengarah saya untuk memahami bersarang vs obat generik non-bersarang, atau bahkan Tvs ?. Sebagian dari masalahnya adalah ketika Andronicus mencapai titik vital dari penjelasannya, ia menolak utas lain yang hanya menggunakan contoh-contoh sepele. Saya berharap mendapat jawaban yang lebih jelas dan lengkap di sini.
jaco0646
1
@ jaco0646 OK. Dokumen "Java Generics FAQs - Type Arguments" oleh Angelika Langer memiliki FAQ berjudul Apa artinya wildcard multi-level (yaitu, bersarang)? . Itu adalah sumber terbaik yang saya tahu untuk menjelaskan masalah yang diangkat dalam pertanyaan OP. Aturan untuk wildcard bersarang tidak mudah atau intuitif.
skomisa
1

Saya akan merekomendasikan Anda untuk melihat dokumentasi wildcard generik terutama panduan untuk penggunaan wildcard

Terus terang metode Anda #doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) {}

dan panggilan seperti

doesntCompile(new HashMap<Integer, List<Integer>>());

Secara fundamental tidak benar

Mari tambahkan implementasi hukum :

    static void doesntCompile(Map<Integer, List<? extends Number>> map) {
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    }

Ini benar-benar baik, karena Double meluas Angka, jadi put List<Double>juga benar-benar baik List<Integer>, kan?

Namun, apakah Anda masih menganggap itu legal untuk lulus new HashMap<Integer, List<Integer>>()dari contoh Anda di sini ?

Compiler tidak berpikir begitu, dan melakukan yang terbaik untuk menghindari situasi seperti itu.

Cobalah untuk melakukan implementasi yang sama dengan metode #compile dan kompiler jelas tidak akan memungkinkan Anda untuk memasukkan daftar ganda ke dalam peta.

    static <T extends Number> void compiles(Map<Integer, List<T>> map) {
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    }

Pada dasarnya Anda dapat menempatkan apa-apa kecuali List<T>bahwa ini mengapa itu aman untuk memanggil metode yang dengan new HashMap<Integer, List<Integer>>()atau new HashMap<Integer, List<Double>>()atau new HashMap<Integer, List<Long>>()atau new HashMap<Integer, List<Number>>().

Jadi singkatnya, Anda mencoba menipu dengan kompiler dan itu cukup bertahan melawan kecurangan tersebut.

NB: jawaban yang diposting oleh Maurice Perry benar sekali. Saya hanya tidak yakin itu cukup jelas, jadi saya mencoba (sangat berharap saya berhasil) untuk menambahkan posting yang lebih luas.

Makhno
sumber