รายการ <Map <String, String >> vs List <? ขยาย Map <String, String >>


129

มีความแตกต่างระหว่าง

List<Map<String, String>>

และ

List<? extends Map<String, String>>

?

ถ้าไม่มีความแตกต่างจะใช้ประโยชน์? extendsอะไร?


2
ฉันรัก java แต่นี่เป็นหนึ่งในสิ่งที่ไม่ดี ...
Mite Mitreski

4
ผมรู้สึกว่าถ้าเราอ่านแบบ "อะไรก็ได้ที่ขยาย ... " มันจะชัดเจน
ขยะ

เหลือเชื่อยอดวิวทะลุ 12K ใน 3 วัน? !!
Eng.Fouad

5
ไปถึงหน้าแรกของ Hacker News ยินดีด้วย.
r3st0r3

1
@ อังกฤษพบที่นี่ ไม่มีในหน้าแรกอีกต่อไป แต่มีเมื่อวานนี้ news.ycombinator.net/item?id=3751901 (เมื่อวานนี้เป็นช่วง
กลางวัน

คำตอบ:


180

ความแตกต่างคือตัวอย่างเช่นก

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 Listof HashMaps ควรเป็นListของMaps แต่มีเหตุผลที่ดีว่าทำไมมันถึงไม่ใช่:

สมมติว่าคุณสามารถทำได้:

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ของMaps


6
ยังคงHashMapเป็นMapเพราะความหลากหลาย
Eng.Fouad

46
ถูกต้อง แต่ a ListของHashMaps ไม่ใช่ListของMaps
จริง

1
สิ่งนี้เรียกว่าBounded quantification
Dan Burton

ตัวอย่างที่ดี สิ่งที่น่าสังเกตก็คือแม้ว่าคุณจะประกาศList<Map<String,String>> maps = hashMaps; และHashMap<String,String> aMap = new HashMap<String, String>();คุณก็ยังพบว่าmaps.add(aMap);ผิดกฎหมายในขณะที่hashMaps.add(aMap);ถูกกฎหมาย จุดประสงค์คือเพื่อป้องกันการเพิ่มประเภทที่ไม่ถูกต้อง แต่จะไม่อนุญาตให้เพิ่มประเภทที่ถูกต้อง (คอมไพเลอร์ไม่สามารถระบุประเภท "ที่ถูกต้อง" ในระหว่างเวลาคอมไพล์)
Raze

@Raze no จริง ๆ แล้วคุณสามารถเพิ่มลงHashMapในรายการMapได้ทั้งสองตัวอย่างของคุณถูกกฎหมายถ้าฉันอ่านอย่างถูกต้อง
จริง

24

คุณไม่สามารถกำหนดนิพจน์ด้วยชนิดเช่นList<NavigableMap<String,String>>แรกได้

(หากคุณต้องการทราบว่าเหตุใดคุณจึงไม่สามารถกำหนดList<String>ให้List<Object>ดูคำถามอื่น ๆ อีกหนึ่งพันล้านคำถามใน SO ได้)


1
สามารถอธิบายเพิ่มเติมได้หรือไม่? ฉันไม่เข้าใจหรือลิงค์ใด ๆ สำหรับการปฏิบัติที่ดี?
Samir Mangroliya

3
@ ซาเมียร์อธิบายว่าอย่างไร? List<String>ไม่ใช่ประเภทย่อยของList<Object>? - ดูตัวอย่างstackoverflow.com/questions/3246137/…
Tom Hawtin - แทคไลน์

2
? extendsนี้ไม่ได้อธิบายสิ่งที่แตกต่างระหว่างมีหรือไม่มี และไม่ได้อธิบายความสัมพันธ์กับ super / subtypes หรือ co / contravariance (ถ้ามี)
Abel

16

สิ่งที่ฉันขาดหายไปในคำตอบอื่น ๆ คือการอ้างอิงว่าสิ่งนี้เกี่ยวข้องกับ 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

contravariance

การกลับกันของความแปรปรวนร่วมเป็นสิ่งที่ตรงกันข้าม ในกรณีที่ความแปรปรวนร่วมประเภทพารามิเตอร์ต้องมีความสัมพันธ์ประเภทย่อยในทางตรงกันข้ามพวกเขาต้องมีความสัมพันธ์แบบซุปเปอร์ไทป์ สิ่งนี้ถือได้ว่าเป็นการสืบทอดขอบเขตบน: อนุญาตให้ใช้ซุปเปอร์ชนิดใดก็ได้และรวมถึงประเภทที่ระบุด้วย:

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>());

ไม่มีอะไรใน "แล้วมันคืออะไรกับ ... " จะรวบรวม
NobleUplift

@NobleUplift: เป็นการยากที่จะช่วยคุณได้หากคุณไม่ได้ระบุข้อความแสดงข้อผิดพลาดที่คุณได้รับ ลองพิจารณาเป็นอีกทางเลือกหนึ่งในการถามคำถามใหม่ใน SO เพื่อให้ได้รับคำตอบซึ่งมีโอกาสประสบความสำเร็จมากขึ้น โค้ดด้านบนเป็นเพียงตัวอย่างโค้ดขึ้นอยู่กับว่าคุณนำไปใช้ในสถานการณ์ของคุณ
Abel

ฉันไม่มีคำถามใหม่ฉันมีการปรับปรุง 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>());ทำงานได้อย่างสมบูรณ์ ตัวอย่างสุดท้ายเห็นได้ชัดว่าถูกต้อง
NobleUplift

@NobleUplift: ขออภัยเดินทางมานานจะแก้ไขเมื่อฉันกลับมาและขอบคุณที่ชี้ให้เห็นข้อผิดพลาดที่เห็นได้ชัด! :)
Abel

ไม่มีปัญหาฉันดีใจที่ได้รับการแก้ไข ฉันได้ทำการแก้ไขให้คุณหากคุณต้องการอนุมัติ
NobleUplift

4

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

ฉันมีวิธีที่หมายถึงการยอมรับได้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ดังนั้นจึงเป็นไปได้ที่จะใช้สิ่งนี้กับคลาสย่อยโดยไม่เป็นอันตรายต่อระบบประเภท


0

ดังที่คุณได้กล่าวไว้อาจมีสองเวอร์ชันด้านล่างของการกำหนดรายการ:

  1. List<? extends Map<String, String>>
  2. List<?>

2 เปิดกว้างมาก สามารถเก็บวัตถุประเภทใดก็ได้ สิ่งนี้อาจไม่มีประโยชน์ในกรณีที่คุณต้องการมีแผนที่ประเภทที่กำหนด Map<String, int>ในกรณีที่มีคนตั้งใจทำให้แตกต่างกันประเภทของแผนที่ตัวอย่างเช่น วิธีการของผู้บริโภคของคุณอาจพัง

ในการสั่งซื้อเพื่อให้แน่ใจว่าListสามารถถือวัตถุชนิดที่กำหนด generics Java ? extendsแนะนำ ดังนั้นใน # 1 Listสามารถเก็บวัตถุใด ๆ ที่ได้มาจากMap<String, String>ประเภท การเพิ่มข้อมูลประเภทอื่นจะทำให้เกิดข้อยกเว้น

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