อะไรคือความแตกต่างระหว่าง ? และวัตถุใน Java generics?


137

ฉันใช้ Eclipse เพื่อช่วยฉันล้างรหัสบางส่วนเพื่อใช้ Java generics อย่างถูกต้อง เวลาส่วนใหญ่มันทำงานได้อย่างยอดเยี่ยมของประเภทอนุมาน แต่มีบางกรณีที่ประเภทอนุมานต้องเป็นประเภททั่วไปที่สุด: วัตถุ แต่ดูเหมือนว่า Eclipse จะให้ทางเลือกแก่ฉันในการเลือกระหว่างประเภทของวัตถุและประเภทของ '?'

ดังนั้นความแตกต่างระหว่าง:

HashMap<String, ?> hash1;

และ

HashMap<String, Object> hash2;

4
ดูกวดวิชาอย่างเป็นทางการในตัวแทน มันอธิบายได้ดีและให้ตัวอย่างว่าทำไมมันจึงมีความจำเป็นมากกว่าการใช้ Object
Ben S

คำตอบ:


148

เป็นตัวอย่างของHashMap<String, String>การแข่งขันแต่ไม่Map<String, ?> Map<String, Object>สมมติว่าคุณต้องการเขียนวิธีการที่ยอมรับแผนที่จากStrings เป็นอะไรก็ได้: ถ้าคุณจะเขียน

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

HashMap<String, String>คุณไม่สามารถจัดหา ถ้าคุณเขียน

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

มันได้ผล!

สิ่งที่บางครั้งเข้าใจผิดในข้อมูลทั่วไปของ Java คือการที่ไม่ได้เป็นชนิดย่อยของList<String> List<Object>(แต่String[]ในความเป็นจริงแล้วเป็นประเภทย่อยObject[]นั่นเป็นหนึ่งในเหตุผลที่ว่าทำไม generics และอาร์เรย์จึงไม่เข้ากัน (อาร์เรย์ใน Java เป็น covariant, generics ไม่ใช่พวกมันคงที่ )

ตัวอย่าง: หากคุณต้องการที่จะเขียนวิธีการที่ยอมรับListของInputStreamและเชื้อInputStreamคุณต้องการเขียน

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

โดยวิธีการ: จาวาที่มีประสิทธิภาพของ Javaเป็นทรัพยากรที่ยอดเยี่ยมเมื่อคุณต้องการที่จะเข้าใจสิ่งที่ไม่ง่ายใน Java (คำถามของคุณข้างต้นได้รับการคุ้มครองอย่างดีในหนังสือ)


1
เป็นวิธีที่ถูกต้องในการใช้ ResponseEntity <?> ในระดับคอนโทรลเลอร์สำหรับฟังก์ชั่นคอนโทรลเลอร์ทั้งหมดหรือไม่
Irakli

คำตอบที่สมบูรณ์แบบโจฮัน!
gaurav

36

วิธีคิดอีกอย่างเกี่ยวกับปัญหานี้ก็คือ

HashMap<String, ?> hash1;

เทียบเท่ากับ

HashMap<String, ? extends Object> hash1;

จับคู่ความรู้นี้กับ "รับและวางหลักการ" ในส่วน (2.4) จากJava Generics และ Collections :

หลักการรับและวาง: ใช้อักขระตัวแทนแบบขยายเมื่อคุณได้รับค่าจากโครงสร้างใช้ super wildcard เมื่อคุณใส่ค่าลงในโครงสร้างเท่านั้นและไม่ใช้สัญลักษณ์แทนเมื่อคุณทั้งสองได้รับและวาง

และการ์ดเสริมอาจเริ่มมีเหตุผลมากขึ้นหวังว่า


1
ถ้า "?" คุณสับสน "? ขยายวัตถุ" อาจทำให้คุณสับสนมากขึ้น อาจจะ.
Michael Myers

พยายามให้ "เครื่องมือคิด" เพื่อให้เหตุผลหนึ่งเกี่ยวกับเรื่องที่ยากลำบากนี้ ให้ข้อมูลเพิ่มเติมเกี่ยวกับการขยายไวด์การ์ด
Julien Chastang

2
ขอบคุณสำหรับข้อมูลเพิ่มเติม ฉันยังคงย่อยอาหาร :)
skiphoppy

HashMap<String, ? extends Object> ดังนั้นจะป้องกันnullการเพิ่มใน hashmap เท่านั้น?
mallaudin

12

เป็นเรื่องง่ายที่จะเข้าใจหากคุณจำได้ว่าCollection<Object>เป็นเพียงคอลเล็กชันทั่วไปที่มีออบเจกต์ประเภทObjectแต่Collection<?>เป็นคอลเลกชันประเภทพิเศษทุกประเภท


1
มีจุดที่จะทำให้สิ่งนี้ไม่ใช่เรื่องง่าย ;-) แต่มันถูกต้อง
Sean Reilly

6

คำตอบแปรปรวนข้างต้นครอบคลุมกรณีส่วนใหญ่ แต่พลาดสิ่งหนึ่ง:

"?" รวมถึง "วัตถุ" ในลำดับชั้นของชั้นเรียน คุณสามารถพูดได้ว่าสตริงเป็นวัตถุชนิดหนึ่งและวัตถุเป็นชนิดของ? ไม่ใช่ทุกอย่างที่ตรงกับวัตถุ แต่ทุกอย่างตรงกัน

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

4

คุณไม่สามารถใส่อะไรลงไปอย่างปลอดภัยMap<String, ?>เพราะคุณไม่รู้ว่าควรจะพิมพ์ค่าอะไร

คุณสามารถใส่วัตถุใด ๆ เป็นเพราะค่าที่เป็นที่รู้จักกันเป็นMap<String, Object>Object


"คุณไม่สามารถใส่อะไรลงในแผนที่ได้อย่างปลอดภัย <String,?>" เท็จ คุณทำได้นั่นคือจุดประสงค์ของมัน
Ben S

3
เบ็นผิดค่าเดียวที่คุณสามารถใส่ลงในคอลเลกชันประเภท <?> นั้นเป็นโมฆะในขณะที่คุณสามารถใส่อะไรลงในคอลเลกชันประเภท <Object>
sk.

2
จากลิงก์ที่ฉันให้ไว้ในคำตอบของฉัน: "เนื่องจากเราไม่ทราบว่าองค์ประกอบประเภทของ c หมายถึงอะไรเราจึงไม่สามารถเพิ่มวัตถุได้" ฉันขอโทษสำหรับข้อมูลที่ผิด
Ben S

1
ปัญหาที่ใหญ่ที่สุดคือฉันไม่เข้าใจว่าเป็นไปได้อย่างไรที่คุณไม่สามารถเพิ่มอะไรลงใน HashMap <String,?> หากเราพิจารณาว่าทุกอย่างยกเว้นวัตถุพื้นฐานเป็นวัตถุ หรือมันเป็นแบบดั้งเดิม?
avalon

1
@avalon ที่อื่นมีการอ้างอิงถึงแผนที่นั้นที่ล้อมรอบ Map<String,Integer>ยกตัวอย่างเช่นมันอาจจะเป็น Integerควรใส่เฉพาะวัตถุที่เก็บไว้ในแผนที่เป็นค่า แต่เนื่องจากคุณไม่ทราบชนิดของค่า (มัน?), คุณไม่ทราบว่าถ้ามันปลอดภัยที่จะโทรput(key, "x"), put(key, 0)หรือสิ่งอื่น
erickson

2

การประกาศhash1เป็นการHashMap<String, ?>กำหนดว่าตัวแปรhash1สามารถเก็บค่าใด ๆHashMapที่มีคีย์Stringและค่าชนิดใดก็ได้

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

ทั้งหมดข้างต้นถูกต้องเนื่องจากตัวแปร mapสามารถเก็บแมปแฮชเหล่านั้นได้ ตัวแปรนั้นไม่สนใจว่าประเภทค่าคืออะไรของแฮชแมปที่เก็บไว้

มีตัวแทนไม่ได้แต่ช่วยให้คุณใส่ชนิดของวัตถุใด ๆ ลงในแผนที่ของคุณ ตามจริงแล้วด้วยแผนที่แฮชข้างบนคุณไม่สามารถใส่อะไรลงไปโดยใช้mapตัวแปร:

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

ทั้งหมดของวิธีการโทรข้างต้นจะส่งผลให้เกิดความผิดพลาดในเวลารวบรวมเพราะ Java ไม่ทราบว่าชนิดราคาภายใน HashMap mapคือ

คุณยังสามารถรับค่าจากแผนที่แฮช แม้ว่าคุณ "ไม่ทราบประเภทของค่า" (เนื่องจากคุณไม่ทราบว่ามีแฮชแผนที่ประเภทใดอยู่ในตัวแปรของคุณ) คุณสามารถพูดได้ว่าทุกอย่างเป็นประเภทย่อยของ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);

บล็อกด้านบนของรหัสจะพิมพ์ 10 ถึงคอนโซล


ดังนั้นหากต้องการปิดให้ใช้ a HashMapพร้อมด้วย wildcard เมื่อคุณไม่สนใจ (เช่นไม่สำคัญ) ประเภทของHashMapตัวอย่างเช่น:

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

มิฉะนั้นให้ระบุประเภทที่คุณต้องการ:

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

ในวิธีการข้างต้นเราจำเป็นต้องรู้ว่ากุญแจของแผนที่นั้นCharacterมิฉะนั้นเราจะไม่รู้ว่าจะใช้ประเภทใดเพื่อรับค่าจากมัน วัตถุทั้งหมดมีtoString()วิธีการดังนั้นแผนที่สามารถมีวัตถุประเภทใดก็ได้สำหรับค่าของมัน เรายังคงสามารถพิมพ์ค่าต่างๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.