มีความแตกต่างระหว่าง
List<Map<String, String>>
และ
List<? extends Map<String, String>>
?
ถ้าไม่มีความแตกต่างจะใช้ประโยชน์? extends
อะไร?
มีความแตกต่างระหว่าง
List<Map<String, String>>
และ
List<? extends Map<String, String>>
?
ถ้าไม่มีความแตกต่างจะใช้ประโยชน์? extends
อะไร?
คำตอบ:
ความแตกต่างคือตัวอย่างเช่นก
List<HashMap<String,String>>
คือ
List<? extends Map<String,String>>
แต่ไม่ใช่
List<Map<String,String>>
ดังนั้น:
void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}
void main( String[] args ){
List<HashMap<String,String>> myMap;
withWilds( myMap ); // Works
noWilds( myMap ); // Compiler error
}
คุณจะคิดว่า a List
of HashMap
s ควรเป็นList
ของMap
s แต่มีเหตุผลที่ดีว่าทำไมมันถึงไม่ใช่:
สมมติว่าคุณสามารถทำได้:
List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();
List<Map<String,String>> maps = hashMaps; // Won't compile,
// but imagine that it could
Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap
maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)
// But maps and hashMaps are the same object, so this should be the same as
hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)
ดังนั้นนี่คือเหตุผลList
ของการHashMap
ไม่ควรจะเป็นList
ของMap
s
HashMap
เป็นMap
เพราะความหลากหลาย
List
ของHashMap
s ไม่ใช่List
ของMap
s
List<Map<String,String>> maps = hashMaps;
และHashMap<String,String> aMap = new HashMap<String, String>();
คุณก็ยังพบว่าmaps.add(aMap);
ผิดกฎหมายในขณะที่hashMaps.add(aMap);
ถูกกฎหมาย จุดประสงค์คือเพื่อป้องกันการเพิ่มประเภทที่ไม่ถูกต้อง แต่จะไม่อนุญาตให้เพิ่มประเภทที่ถูกต้อง (คอมไพเลอร์ไม่สามารถระบุประเภท "ที่ถูกต้อง" ในระหว่างเวลาคอมไพล์)
HashMap
ในรายการMap
ได้ทั้งสองตัวอย่างของคุณถูกกฎหมายถ้าฉันอ่านอย่างถูกต้อง
คุณไม่สามารถกำหนดนิพจน์ด้วยชนิดเช่นList<NavigableMap<String,String>>
แรกได้
(หากคุณต้องการทราบว่าเหตุใดคุณจึงไม่สามารถกำหนดList<String>
ให้List<Object>
ดูคำถามอื่น ๆ อีกหนึ่งพันล้านคำถามใน SO ได้)
List<String>
ไม่ใช่ประเภทย่อยของList<Object>
? - ดูตัวอย่างstackoverflow.com/questions/3246137/…
? extends
นี้ไม่ได้อธิบายสิ่งที่แตกต่างระหว่างมีหรือไม่มี และไม่ได้อธิบายความสัมพันธ์กับ super / subtypes หรือ co / contravariance (ถ้ามี)
สิ่งที่ฉันขาดหายไปในคำตอบอื่น ๆ คือการอ้างอิงว่าสิ่งนี้เกี่ยวข้องกับ co- และ contravariance และ sub- และ supertypes (นั่นคือ polymorphism) โดยทั่วไปและโดยเฉพาะกับ Java โดยเฉพาะ สิ่งนี้อาจเป็นที่เข้าใจกันดีโดย OP แต่ในกรณีนี้มันจะไป:
หากคุณมีคลาสAutomobile
แล้วCar
และTruck
เป็นประเภทย่อยของพวกเขา รถยนต์ใด ๆ สามารถกำหนดให้กับตัวแปรประเภทรถยนต์ซึ่งเป็นที่รู้จักกันดีใน OO และเรียกว่าความหลากหลาย ความแปรปรวนร่วมหมายถึงการใช้หลักการเดียวกันนี้ในสถานการณ์ที่มีบุคคลทั่วไปหรือผู้รับมอบสิทธิ์ Java ไม่มีผู้รับมอบสิทธิ์ (ยัง) ดังนั้นคำนี้ใช้กับ generics เท่านั้น
ฉันมักจะคิดว่าความแปรปรวนร่วมเป็นความหลากหลายมาตรฐานสิ่งที่คุณคาดหวังว่าจะได้ผลโดยไม่ต้องคิดเพราะ:
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
อย่างไรก็ตามเหตุผลของข้อผิดพลาดคือถูกต้อง: List<Car>
ไม่ได้รับมรดกจากList<Automobile>
และไม่สามารถกำหนดให้กันและกันได้ เฉพาะพารามิเตอร์ประเภททั่วไปเท่านั้นที่มีความสัมพันธ์แบบสืบทอด อาจมีคนคิดว่าคอมไพเลอร์ Java ไม่ฉลาดพอที่จะเข้าใจสถานการณ์ของคุณที่นั่นอย่างถูกต้อง อย่างไรก็ตามคุณสามารถช่วยคอมไพเลอร์ได้โดยให้คำแนะนำแก่เขา:
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
การกลับกันของความแปรปรวนร่วมเป็นสิ่งที่ตรงกันข้าม ในกรณีที่ความแปรปรวนร่วมประเภทพารามิเตอร์ต้องมีความสัมพันธ์ประเภทย่อยในทางตรงกันข้ามพวกเขาต้องมีความสัมพันธ์แบบซุปเปอร์ไทป์ สิ่งนี้ถือได้ว่าเป็นการสืบทอดขอบเขตบน: อนุญาตให้ใช้ซุปเปอร์ชนิดใดก็ได้และรวมถึงประเภทที่ระบุด้วย:
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
สามารถใช้กับCollections.sort :
public static <T> void sort(List<T> list, Comparator<? super T> c)
// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());
คุณสามารถเรียกมันด้วยตัวเปรียบเทียบที่เปรียบเทียบวัตถุและใช้กับประเภทใดก็ได้
บางทีคุณอาจจะไม่ได้ถาม แต่มันช่วยให้เข้าใจในการตอบคำถามของคุณ โดยทั่วไปเมื่อคุณได้รับบางสิ่งบางอย่างให้ใช้ความแปรปรวนร่วมและเมื่อคุณใส่อะไรลงไปให้ใช้ความแตกต่าง สิ่งนี้อธิบายได้ดีที่สุดในคำตอบสำหรับคำถาม Stack Overflow จะใช้ความแตกต่างใน Java generics ได้อย่างไร? .
List<? extends Map<String, String>>
คุณสามารถใช้extends
เพื่อให้กฎระเบียบสำหรับการแปรปรวนมีผลบังคับใช้ ที่นี่คุณมีรายการแผนที่และแต่ละรายการที่คุณจัดเก็บในรายการจะต้องเป็นMap<string, string>
หรือได้มาจากแผนที่นั้น คำสั่งList<Map<String, String>>
ไม่ได้มาจากMap
แต่ต้องเป็นไฟล์ Map
.
ดังนั้นสิ่งต่อไปนี้จะใช้งานได้เนื่องจากTreeMap
สืบทอดมาจากMap
:
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
แต่สิ่งนี้จะไม่:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
และสิ่งนี้จะไม่ได้ผลเช่นกันเนื่องจากไม่เป็นไปตามข้อ จำกัด ของความแปรปรวนร่วม:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>()); // This is NOT allowed, List does not implement Map
สิ่งนี้อาจชัดเจน แต่คุณอาจสังเกตแล้วว่าการใช้extends
คีย์เวิร์ดจะใช้กับพารามิเตอร์นั้นเท่านั้นไม่ใช่กับพารามิเตอร์ที่เหลือ กล่าวคือสิ่งต่อไปนี้จะไม่รวบรวม:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
สมมติว่าคุณต้องการอนุญาตประเภทใดก็ได้ในแผนที่โดยใช้คีย์เป็นสตริงคุณสามารถใช้extend
กับพารามิเตอร์แต่ละประเภทได้ กล่าวคือสมมติว่าคุณประมวลผล XML และต้องการจัดเก็บ AttrNode, Element และอื่น ๆ ในแผนที่คุณสามารถทำสิ่งต่างๆเช่น:
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());
ผลลัพธ์ในfound: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without bounds
. List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());
ทำงานได้อย่างสมบูรณ์ ตัวอย่างสุดท้ายเห็นได้ชัดว่าถูกต้อง
วันนี้ฉันใช้ฟีเจอร์นี้แล้วนี่คือตัวอย่างชีวิตจริงที่สดใหม่ของฉัน (ฉันเปลี่ยนชื่อคลาสและเมธอดเป็นชื่อสามัญเพื่อไม่ให้เบี่ยงเบนความสนใจไปจากจุดที่แท้จริง)
ฉันมีวิธีที่หมายถึงการยอมรับได้Set
ของA
วัตถุที่ฉันเดิมเขียนด้วยลายเซ็นนี้:
void myMethod(Set<A> set)
แต่มันต้องการเรียกมันด้วยSet
คลาสย่อยของA
. แต่นี่ห้าม! (เหตุผลก็คือmyMethod
สามารถเพิ่มอ็อบเจกต์set
ที่เป็นประเภทA
ได้ แต่ไม่ใช่ประเภทย่อยที่set
อ็อบเจ็กต์ถูกประกาศว่าอยู่ที่ไซต์ของผู้โทรดังนั้นสิ่งนี้อาจทำให้ระบบประเภทแตกได้ถ้าเป็นไปได้)
ตอนนี้มาที่นี่เพื่อช่วยชีวิตเพราะมันทำงานได้ตามที่ตั้งใจไว้ถ้าฉันใช้ลายเซ็นวิธีนี้แทน:
<T extends A> void myMethod(Set<T> set)
หรือสั้นกว่าหากคุณไม่จำเป็นต้องใช้ประเภทจริงในเนื้อหาวิธีการ:
void myMethod(Set<? extends A> set)
ด้วยวิธีนี้set
ประเภทของจะกลายเป็นคอลเลกชันของอ็อบเจ็กต์ของประเภทย่อยที่แท้จริงA
ดังนั้นจึงเป็นไปได้ที่จะใช้สิ่งนี้กับคลาสย่อยโดยไม่เป็นอันตรายต่อระบบประเภท
ดังที่คุณได้กล่าวไว้อาจมีสองเวอร์ชันด้านล่างของการกำหนดรายการ:
List<? extends Map<String, String>>
List<?>
2 เปิดกว้างมาก สามารถเก็บวัตถุประเภทใดก็ได้ สิ่งนี้อาจไม่มีประโยชน์ในกรณีที่คุณต้องการมีแผนที่ประเภทที่กำหนด Map<String, int>
ในกรณีที่มีคนตั้งใจทำให้แตกต่างกันประเภทของแผนที่ตัวอย่างเช่น วิธีการของผู้บริโภคของคุณอาจพัง
ในการสั่งซื้อเพื่อให้แน่ใจว่าList
สามารถถือวัตถุชนิดที่กำหนด generics Java ? extends
แนะนำ ดังนั้นใน # 1 List
สามารถเก็บวัตถุใด ๆ ที่ได้มาจากMap<String, String>
ประเภท การเพิ่มข้อมูลประเภทอื่นจะทำให้เกิดข้อยกเว้น