Apa perbedaan antara ? dan Objek di generik Java?

141

Saya menggunakan Eclipse untuk membantu saya membersihkan beberapa kode untuk menggunakan Java generics dengan benar. Sebagian besar waktu itu melakukan pekerjaan yang sangat baik dalam menyimpulkan tipe, tetapi ada beberapa kasus di mana tipe yang disimpulkan harus generik mungkin: Objek. Tapi Eclipse sepertinya memberiku pilihan untuk memilih antara tipe Object dan tipe '?'.

Jadi apa perbedaan antara:

HashMap<String, ?> hash1;

dan

HashMap<String, Object> hash2;
cakrawala
sumber
4
Lihat tutorial resmi tentang Wildcard . Ini menjelaskannya dengan baik dan memberikan contoh mengapa itu perlu daripada hanya menggunakan Object.
Ben S

Jawaban:

154

Contoh HashMap<String, String>pertandingan Map<String, ?>tetapi tidak Map<String, Object>. Katakanlah Anda ingin menulis metode yang menerima peta dari Strings ke apapun: Jika Anda mau menulis

public void foobar(Map<String, Object> ms) {
    ...
}

Anda tidak dapat menyediakan HashMap<String, String>. Jika Anda menulis

public void foobar(Map<String, ?> ms) {
    ...
}

berhasil!

Suatu hal yang terkadang disalahpahami dalam generik Java adalah bahwa List<String>itu bukan subtipe dari List<Object>. (Tetapi String[]sebenarnya adalah subtipe dari Object[], itulah salah satu alasan mengapa generik dan array tidak bercampur dengan baik. (Array di Java adalah kovarian, bukan generik, mereka tidak berubah )).

Contoh: Jika Anda ingin menulis metode yang menerima Lists dari InputStreamdan subtipe InputStream, Anda akan menulis

public void foobar(List<? extends InputStream> ms) {
    ...
}

Ngomong-ngomong: Java Efektif Joshua Bloch adalah sumber yang sangat baik ketika Anda ingin memahami hal-hal yang tidak sesederhana itu di Java. (Pertanyaan Anda di atas juga dibahas dengan sangat baik dalam buku ini.)

Johannes Weiss
sumber
1
apakah ini cara yang benar untuk menggunakan ResponseEntity <?> pada tingkat pengontrol untuk semua fungsi pengontrol saya?
Irakli
jawaban tanpa cela Johannes!
Gaurav
39

Cara lain untuk memikirkan masalah ini adalah itu

HashMap<String, ?> hash1;

setara dengan

HashMap<String, ? extends Object> hash1;

Gabungkan pengetahuan ini dengan "Prinsip Dapatkan dan Taruh" di bagian (2.4) dari Java Generics and Collections :

Prinsip Get and Put: gunakan karakter pengganti extends saat Anda hanya mendapatkan nilai dari struktur, gunakan karakter pengganti super saat Anda hanya memasukkan nilai ke dalam struktur, dan jangan gunakan karakter pengganti saat Anda mendapatkan dan memasukkan.

dan kartu liar mungkin mulai lebih masuk akal, semoga.

Julien Chastang
sumber
1
Jika "?" membingungkan Anda, "? extends Object" kemungkinan akan lebih membingungkan Anda. Mungkin.
Michael Myers
Mencoba menyediakan "alat berpikir" untuk memungkinkan seseorang bernalar tentang subjek yang sulit ini. Memberikan info tambahan tentang memperluas karakter pengganti.
Julien Chastang
2
Terima kasih atas info tambahannya. Saya masih mencernanya. :)
skiphoppy
HashMap<String, ? extends Object> jadi hanya mencegah nulluntuk ditambahkan di hashmap?
mallaudin
13

Sangat mudah untuk dipahami jika Anda mengingat bahwa Collection<Object>itu hanyalah koleksi umum yang berisi objek bertipe Object, tetapi Collection<?>merupakan tipe super dari semua jenis koleksi.

topchef
sumber
2
Ada poin yang harus dibuat bahwa ini tidak mudah ;-), tetapi itu benar.
Sean Reilly
6

Jawaban di atas kovarians mencakup sebagian besar kasus tetapi melewatkan satu hal:

"?" termasuk "Objek" dalam hierarki kelas. Bisa dikatakan bahwa String adalah tipe Object dan Object adalah tipe?. Tidak semuanya cocok dengan Object, tapi semuanya cocok?.

int test1(List<?> l) {
  return l.size();
}

int test2(List<Object> l) {
  return l.size();
}

List<?> l1 = Lists.newArrayList();
List<Object> l2 = Lists.newArrayList();
test1(l1);  // compiles because any list will work
test1(l2);  // compiles because any list will work
test2(l1);  // fails because a ? might not be an Object
test2(l2);  // compiled because Object matches Object
Eyal
sumber
4

Anda tidak dapat memasukkan apa pun dengan aman Map<String, ?>, karena Anda tidak tahu jenis nilai yang seharusnya.

Anda dapat memasukkan objek apa pun ke dalam a Map<String, Object>, karena nilainya dikenal sebagai Object.

erickson
sumber
"Anda tidak dapat dengan aman memasukkan apa pun ke dalam Peta <String,?>" Salah. Anda BISA, itulah tujuannya.
Ben S
3
Ben salah, satu-satunya nilai yang dapat Anda masukkan ke dalam kumpulan tipe <?> Adalah null, sedangkan Anda dapat memasukkan apa pun ke dalam kumpulan tipe <Object>.
sk.
2
Dari link yang saya berikan dalam jawaban saya: "Karena kita tidak tahu apa kepanjangan dari tipe c, kita tidak dapat menambahkan objek ke dalamnya.". Saya minta maaf atas informasi yang salah.
Ben S
1
masalah terbesar adalah saya tidak mengerti bagaimana mungkin Anda tidak dapat menambahkan apapun ke dalam HashMap <String,?>, jika kita menganggap bahwa SEMUANYA kecuali primitif adalah objek. Atau untuk kaum primitif?
Avalon
1
@avalon Di tempat lain, ada referensi ke peta itu yang dibatasi. Misalnya, mungkin a Map<String,Integer>. Hanya Integerobjek yang harus disimpan di peta sebagai nilai. Tapi karena Anda tidak tahu jenis nilai (itu ?), Anda tidak tahu apakah apakah itu aman untuk memanggil put(key, "x"), put(key, 0), atau apa pun.
erickson
2

Mendeklarasikan hash1sebagai HashMap<String, ?>menentukan bahwa variabel hash1dapat menampung apa saja HashMapyang memiliki kunci Stringdan jenis nilai apa pun.

HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();

Semua hal di atas valid, karena variabel map dapat menyimpan peta hash mana pun. Variabel itu tidak peduli apa jenis Nilai, dari hashmap yang dimilikinya.

Memiliki wildcard tidak tidak , bagaimanapun, membiarkan Anda menempatkan jenis objek ke dalam peta Anda. Faktanya, dengan hash map di atas, Anda tidak dapat memasukkan apa pun ke dalamnya menggunakan mapvariabel:

map.put("A", new Integer(0));
map.put("B", new Object());
map.put("C", "Some String");

Semua panggilan metode di atas akan menghasilkan kesalahan waktu kompilasi karena Java tidak tahu apa tipe Nilai dari HashMap di dalamnya map.

Anda masih bisa mendapatkan nilai dari peta hash. Meskipun Anda "tidak tahu jenis nilainya", (karena Anda tidak tahu jenis peta hash apa yang ada di dalam variabel Anda), Anda dapat mengatakan bahwa semuanya adalah subkelas dari Objectdan, jadi, apa pun yang Anda dapatkan dari peta akan menjadi tipe Object:

HashMap<String, Integer> myMap = new HashMap<>();// This variable is used to put things into the map.

myMap.put("ABC", 10);

HashMap<String, ?> map = myMap;
Object output = map.get("ABC");// Valid code; Object is the superclass of everything, (including whatever is stored our hash map).

System.out.println(output);

Blok kode di atas akan mencetak 10 ke konsol.


Jadi, untuk menyelesaikannya, gunakan a HashMapwith wildcard bila Anda tidak peduli (tidak masalah) apa jenisnya HashMap, misalnya:

public static void printHashMapSize(Map<?, ?> anyMap) {
    // This code doesn't care what type of HashMap is inside anyMap.
    System.out.println(anyMap.size());
}

Jika tidak, tentukan jenis yang Anda butuhkan:

public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
    for (int i = 'A'; i <= 'Z'; i++)
        System.out.println(anyCharacterMap.get((char) i));
}

Dalam metode di atas, kita perlu tahu bahwa kunci Map adalah a Character, jika tidak, kita tidak akan tahu tipe apa yang digunakan untuk mendapatkan nilai darinya. Semua objek memiliki toString()metode, bagaimanapun, sehingga peta dapat memiliki tipe objek apa pun untuk nilainya. Kami masih bisa mencetak nilainya.

Kröw
sumber