เหตุใด Java Map จึงไม่ขยายการรวบรวม


146

ฉันรู้สึกประหลาดใจโดยความจริงที่ว่าไม่ได้เป็นMap<?,?>Collection<?>

ฉันคิดว่ามันคงสมเหตุสมผลถ้ามีการประกาศเช่นนี้:

public interface Map<K,V> extends Collection<Map.Entry<K,V>>

ท้ายที่สุดแล้ว a Map<K,V>คือชุดของMap.Entry<K,V>ใช่ไหม

ดังนั้นมีเหตุผลที่ดีว่าทำไมจึงไม่ใช้งานเช่นนี้


ขอบคุณ Cletus สำหรับคำตอบที่เชื่อถือได้มากที่สุด แต่ฉันยังคงสงสัยว่าทำไมหากคุณสามารถดูMap<K,V>เป็นSet<Map.Entries<K,V>>(ผ่านentrySet()) มันไม่เพียงขยายส่วนต่อประสานนั้นแทน

ถ้า a MapคือCollectionอะไรองค์ประกอบคืออะไร คำตอบที่สมเหตุสมผลคือ "คู่ของคีย์ - ค่า"

ว่าinterface Map<K,V> extends Set<Map.Entry<K,V>>จะดี!

แต่สิ่งนี้ให้สิ่งที่เป็นMapนามธรรมที่จำกัด (และไม่มีประโยชน์อย่างยิ่ง)

แต่ถ้าเป็นอย่างนั้นทำไมentrySetอินเทอร์เฟซถูกระบุไว้ทำไม? มันจะต้องมีประโยชน์อย่างใด (และฉันคิดว่ามันง่ายที่จะเถียงกับตำแหน่งนั้น!)

คุณไม่สามารถถามค่าคีย์แผนที่ที่กำหนดและไม่สามารถลบรายการสำหรับคีย์ที่กำหนดโดยไม่ทราบว่าคีย์แมปนั้นให้คุณค่าอะไร

ฉันไม่ได้บอกว่านั่นคือทั้งหมดที่มีให้Map! สามารถและควรรักษาวิธีอื่น ๆ ทั้งหมด (ยกเว้นentrySetซึ่งซ้ำซ้อนในขณะนี้)!


1
ชุดเหตุการณ์ <Map.Entry <K, V >> จริง ๆ แล้ว
Maurice Perry

3
มันแค่ทำไม่ได้ มันขึ้นอยู่กับความคิดเห็น (ตามที่อธิบายไว้ในคำถามที่พบบ่อยเกี่ยวกับการออกแบบ) แทนที่จะเป็นข้อสรุปเชิงตรรกะ สำหรับแนวทางอื่นให้ดูที่การออกแบบภาชนะบรรจุใน C ++ STL ( sgi.com/tech/stl/table_of_contents.html ) ซึ่งขึ้นอยู่กับการวิเคราะห์ที่ละเอียดและยอดเยี่ยมของ Stepanov
beldaz

คำตอบ:


123

จากคำถามที่พบบ่อยเกี่ยวกับการออกแบบ Java Collections API :

เหตุใดแผนที่จึงไม่ขยายการรวบรวม

นี่คือโดยการออกแบบ เรารู้สึกว่าการจับคู่นั้นไม่ใช่การรวบรวมและการรวบรวมไม่ใช่การจับคู่ ดังนั้นจึงไม่มีความจำเป็นที่ Map จะขยายส่วนต่อประสาน Collection (หรือกลับกัน)

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

อาจมีการสะสมเพื่อขยายแผนที่ แต่นี่ทำให้เกิดคำถาม: กุญแจคืออะไร? ไม่มีคำตอบที่น่าพอใจและการบังคับให้นำไปสู่อินเทอร์เฟซที่ผิดธรรมชาติ

แผนที่สามารถดูได้เป็นคอลเลกชัน (ของคีย์ค่าหรือคู่) และความจริงข้อนี้สะท้อนให้เห็นใน "การดำเนินการเรียกดูคอลเลกชัน" สามรายการบนแผนที่ (ชุดคีย์ชุดค่าและค่า) แม้ว่าโดยทั่วไปแล้วจะเป็นไปได้ที่จะดูรายการเป็นดัชนีการแมปแผนที่กับองค์ประกอบ แต่ก็มีคุณสมบัติที่น่ารังเกียจที่การลบองค์ประกอบจากรายการจะเปลี่ยนคีย์ที่เชื่อมโยงกับองค์ประกอบทั้งหมดก่อนองค์ประกอบที่ถูกลบ นั่นเป็นเหตุผลที่เราไม่ได้มีการดำเนินการดูแผนที่ในรายการ

ปรับปรุง:ฉันคิดว่าคำพูดตอบคำถามส่วนใหญ่ มันคุ้มค่าที่จะเน้นส่วนที่เกี่ยวกับการรวบรวมรายการที่ไม่เป็นประโยชน์โดยเฉพาะอย่างยิ่ง ตัวอย่างเช่น:

Set<Map.Entry<String,String>>

จะอนุญาต:

set.add(entry("hello", "world"));
set.add(entry("hello", "world 2");

(สมมติว่าentry()วิธีการสร้างMap.Entryอินสแตนซ์)

Mapต้องการคีย์ที่ไม่ซ้ำดังนั้นนี่จะเป็นการละเมิด หรือถ้าคุณกำหนดคีย์ที่ไม่ซ้ำใครให้กับSetรายการมันไม่ได้เป็นSetความหมายทั่วไป มันSetมีข้อ จำกัด เพิ่มเติม

คุณสามารถพูดได้ว่าequals()/ hashCode()ความสัมพันธ์สำหรับMap.Entryคีย์ล้วนๆ แต่ก็มีปัญหา ที่สำคัญกว่านั้นมันเพิ่มมูลค่าหรือไม่? คุณอาจพบว่าสิ่งที่เป็นนามธรรมนี้พังลงเมื่อคุณเริ่มมองที่มุมกรณี

เป็นที่น่าสังเกตว่าHashSetมีการใช้งานจริงในฐานะHashMapไม่ใช่วิธีอื่น นี่คือรายละเอียดการนำไปใช้อย่างหมดจด แต่ก็น่าสนใจ

เหตุผลหลักสำหรับentrySet()การมีอยู่คือการลดความซับซ้อนของการแวะผ่านดังนั้นคุณไม่จำเป็นต้องข้ามปุ่มจากนั้นทำการค้นหากุญแจ อย่าถือเป็นหลักฐานเบื้องต้นว่า a Mapควรเป็นSetรายการ (imho)


1
การอัปเดตนั้นน่าเชื่อถือมาก แต่จริงๆแล้วการตั้งค่ามุมมองที่ส่งกลับโดยentrySet()ไม่สนับสนุนremoveในขณะที่ไม่รองรับadd(อาจจะพ่นUnsupportedException) ดังนั้นฉันเห็นจุดของคุณ แต่แล้วอีกครั้งฉันเห็นจุดของ OP ด้วย .. (ความเห็นของฉันเองตามที่ระบุไว้ในคำตอบของฉันเอง .. )
Enno Shioji

1
ซวี่นำจุดที่ดี ภาวะแทรกซ้อนทั้งหมดเหล่านี้เนื่องจากaddสามารถจัดการได้เช่นเดียวกับentrySet()มุมมอง: อนุญาตการดำเนินการบางอย่างและไม่อนุญาตให้ผู้อื่น แน่นอนว่าการตอบสนองตามธรรมชาติคือ "สิ่งใดบ้างที่Setไม่สนับสนุนadd" - ดีถ้าพฤติกรรมดังกล่าวเป็นที่ยอมรับได้entrySet()แล้วทำไมมันไม่เป็นที่ยอมรับthis? ที่กล่าวว่าฉันกำลังเชื่อว่าส่วนใหญ่แล้วว่าทำไมถึงไม่ได้เป็นเช่นความคิดที่ดีที่สุดเท่าที่ผมเคยคิด แต่ผมยังคิดว่ามันคุ้มค่าของการอภิปรายต่อไปถ้าเพียง แต่จะเสริมสร้างความเข้าใจของตัวเองของสิ่งที่ทำให้ดี API / ออกแบบ OOP
polygenelubricants

3
ย่อหน้าแรกของการอ้างอิงจากคำถามที่พบบ่อยเป็นเรื่องตลก: "เรารู้สึก ... ดังนั้น ... ทำให้รู้สึกเล็กน้อย" ฉันไม่รู้สึกว่า "รู้สึก" และ "ความรู้สึก" เป็นบทสรุป; o)
Peter Wippermann

สามารถสะสมเพื่อขยายแผนที่ได้หรือไม่ ไม่แน่ใจว่าทำไมผู้เขียนคิดว่าสำหรับการCollectionขยายMap?
แลกเปลี่ยน

11

ในขณะที่คุณได้รับคำตอบจำนวนหนึ่งที่ครอบคลุมคำถามของคุณโดยตรงฉันคิดว่าอาจเป็นประโยชน์ในการถอยกลับเล็กน้อยและดูคำถามโดยทั่วไปมากกว่าเดิม นั่นคือไม่ต้องดูว่าไลบรารี Java ถูกเขียนอย่างไรและดูว่าทำไมมันถึงถูกเขียนขึ้น

ปัญหาที่เกิดขึ้นที่นี่เป็นที่มรดกเพียงรูปแบบหนึ่งในประเภทของคนธรรมดาสามัญ หากคุณเลือกสองสิ่งที่ทั้งคู่ดูเหมือน "เหมือนคอลเล็กชัน" คุณอาจเลือกสิ่งที่พวกเขามีเหมือนกัน 8 หรือ 10 อย่าง หากคุณเลือกสิ่งที่ "เหมือนคอลเลกชัน" ที่แตกต่างกันพวกเขาก็จะร่วมกัน 8 หรือ 10 สิ่งที่เหมือนกัน - แต่พวกเขาจะไม่เหมือนกัน 8 หรือ 10 สิ่งที่เป็นคู่แรก

หากคุณดูสิ่งที่ "เหมือนคอลเลคชั่น" หลายโหลหรือแตกต่างกันแทบทุกคนอาจมีลักษณะคล้าย 8 หรือ 10 ลักษณะเหมือนกันอย่างน้อยหนึ่งอัน - แต่ถ้าคุณดูสิ่งที่แบ่งปันกันทุกคน ของพวกเขาคุณจะไม่เหลืออะไรเลย

นี่เป็นสถานการณ์ที่การสืบทอด (โดยเฉพาะการสืบทอดเดี่ยว) ไม่ดีเท่าที่ควร ไม่มีเส้นแบ่งที่ชัดเจนระหว่างสิ่งเหล่านั้นเป็นของสะสมจริงๆและไม่ใช่ - แต่ถ้าคุณต้องการกำหนดคลาสของคอลเลกชันที่มีความหมาย หากคุณเหลือเพียงไม่กี่คลาส Collection ของคุณจะสามารถให้อินเทอร์เฟซที่ค่อนข้างกระจัดกระจายได้ หากคุณออกไปมากขึ้นคุณจะสามารถให้ส่วนต่อประสานที่สมบูรณ์ยิ่งขึ้น

บางคนเลือกที่จะพูดโดยทั่วไปว่า: "คอลเลกชันประเภทนี้รองรับการทำงาน X แต่คุณไม่ได้รับอนุญาตให้ใช้โดยได้มาจากคลาสพื้นฐานที่กำหนด X แต่พยายามใช้คลาส X ที่ได้รับมาล้มเหลว (เช่น โดยการโยนข้อยกเว้น)

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


10

ฉันเดาว่าทำไมถึงเป็นเรื่องส่วนตัว

ใน C # ฉันคิดว่าจะDictionaryขยายหรืออย่างน้อยก็ใช้ชุดสะสม:

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, 
    ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, 
    IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback

ใน Pharo Smalltak เช่นกัน:

Collection subclass: #Set
Set subclass: #Dictionary

แต่มีความไม่สมมาตรกับวิธีการบางอย่าง ตัวอย่างเช่นcollect:จะใช้การเชื่อมโยง (รายการเทียบเท่า) ในขณะที่do:รับค่า พวกเขามีวิธีอื่นในการkeysAndValuesDo:ทำซ้ำพจนานุกรมตามรายการ Add:เข้าร่วมสมาคม แต่remove:ถูก "ระงับ":

remove: anObject
self shouldNotImplement 

ดังนั้นจึงเป็นไปได้แน่นอน แต่นำไปสู่ปัญหาอื่น ๆ ที่เกี่ยวข้องกับลำดับชั้นของชั้นเรียน

สิ่งที่ดีกว่าคือความรู้สึกส่วนตัว


3

คำตอบของ cletus นั้นดี แต่ฉันต้องการเพิ่ม semantic approach หากต้องการรวมทั้งสองอย่างเข้าด้วยกันให้นึกถึงกรณีที่คุณเพิ่มคู่คีย์ - ค่า - ผ่านส่วนต่อการรวบรวมและคีย์มีอยู่แล้ว Map-interface อนุญาตเพียงหนึ่งค่าที่เกี่ยวข้องกับคีย์ แต่ถ้าคุณลบรายการที่มีอยู่ด้วยคีย์เดียวกันโดยอัตโนมัติคอลเล็กชันจะมีขนาดเพิ่มขึ้นเหมือนเมื่อก่อน - โดยไม่คาดคิดสำหรับคอลเลกชัน


4
นี่คือวิธีSetการทำงานและSet ไม่Collectionดำเนินการ
Lii

2

คอลเลกชัน Java หัก มีอินเทอร์เฟซที่ขาดหายไปนั่นคือความสัมพันธ์ ดังนั้นแผนที่ขยายความสัมพันธ์ขยายชุด ความสัมพันธ์ (หรือที่เรียกว่าหลายแผนที่) มีคู่ค่าชื่อที่ไม่ซ้ำกัน แผนที่ (aka "ฟังก์ชั่น") มีชื่อที่ไม่ซ้ำกัน (หรือคีย์) ซึ่งแน่นอนว่าแผนที่จับคู่กับค่า ลำดับขยายแผนที่ (โดยที่แต่ละคีย์เป็นจำนวนเต็ม> 0) กระเป๋า (หรือหลายชุด) ขยายแผนที่ (โดยที่แต่ละปุ่มเป็นองค์ประกอบและแต่ละค่าคือจำนวนครั้งที่องค์ประกอบปรากฏในกระเป๋า)

โครงสร้างนี้จะอนุญาตให้มีการแยกสหภาพ ฯลฯ ของช่วง "คอลเลกชัน" ดังนั้นลำดับชั้นควรจะ:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - โปรดทำให้ถูกต้องในครั้งต่อไป ขอบคุณ


4
ฉันต้องการดู API ของอินเทอร์เฟซสำหรับจินตนาการเหล่านี้อย่างละเอียด ไม่แน่ใจว่าฉันต้องการลำดับ (รายการที่รู้จัก) ที่สำคัญคือจำนวนเต็ม; ฟังดูเหมือนจะเป็นการแสดงที่ยอดเยี่ยมมาก
Lawrence Dol

ถูกต้องลำดับคือรายการ - ดังนั้นลำดับจึงขยายรายการ ฉันไม่รู้ว่าคุณสามารถเข้าถึงสิ่งนี้ได้หรือไม่ แต่อาจเป็นที่น่าสนใจ portal.acm.org/citation.cfm?id=1838687.1838705
xagyg

@SoftwareMonkey นี่คือลิงค์อื่น ลิงก์สุดท้ายคือลิงก์ไปยัง javadoc ของ API zedlib.sourceforge.net
xagyg

@ SoftwareMonkey ฉันยอมรับอย่างไม่อายหน้าว่าการออกแบบเป็นความกังวลหลักของฉันที่นี่ ฉันสมมติว่าปรมาจารย์ที่ Oracle / Sun สามารถปรับ heck ออกจากมันได้
xagyg

ฉันมักจะไม่เห็นด้วย จะถือได้อย่างไรว่า "Map is-a Set" หากคุณไม่สามารถเพิ่มองค์ประกอบที่ไม่ใช่สมาชิกของโดเมนในชุดของคุณได้ (เช่นในตัวอย่างใน @cletus 'answer)
einpoklum

1

ถ้าคุณดูที่โครงสร้างข้อมูลนั้นคุณสามารถคาดเดาได้ว่าทำไมไม่ได้เป็นส่วนหนึ่งของMap Collectionแต่ละCollectionร้านจะเก็บค่าเดียวโดยที่เป็นMapคู่ของคีย์ - ค่า ดังนั้นเมธอดในCollectionอินเตอร์เฟสจะไม่สามารถใช้ร่วมกับMapอินเตอร์เฟสได้ ยกตัวอย่างเช่นในการที่เรามีCollection สิ่งที่จะดำเนินการดังกล่าวในadd(Object o) Mapไม่สมเหตุสมผลที่จะมีวิธีการเช่นMapนี้ แต่เรามีวิธีการในการput(key,value)Map

การโต้เถียงกันไปสำหรับaddAll(), remove()และremoveAll()วิธีการ ดังนั้นเหตุผลหลักคือความแตกต่างในวิธีที่ข้อมูลจะถูกเก็บไว้ในและMap Collectionนอกจากนี้ถ้าคุณจำได้Collectionดำเนินการอินเตอร์เฟซที่Iterableอินเตอร์เฟซคืออินเตอร์เฟซใด ๆ กับ.iterator()วิธีการที่ควรจะกลับ iterator Collectionซึ่งจะต้องช่วยให้เราสามารถย้ำกว่าค่าที่เก็บไว้ใน ตอนนี้วิธีการดังกล่าวจะกลับมาสำหรับMapอะไร? Key iterator หรือ Value iterator? มันก็ไม่สมเหตุสมผลเช่นกัน

มีวิธีการที่เราสามารถทำซ้ำมากกว่าคีย์และค่าที่เก็บในMapและนั่นคือวิธีที่มันเป็นส่วนหนึ่งของCollectionกรอบ


There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.คุณสามารถแสดงตัวอย่างรหัสสำหรับสิ่งนี้ได้ไหม
RamenChef

นั่นเป็นคำอธิบายที่ดีมาก ฉันเข้าใจแล้ว. ขอบคุณ!
Emir Memic

การใช้งานของadd(Object o)จะเป็นการเพิ่มEntry<ClassA, ClassB>วัตถุ A Mapถือได้ว่าCollection of Tuples
LIvanov

0

ว่าinterface Map<K,V> extends Set<Map.Entry<K,V>>จะดี!

ที่จริงแล้วถ้าเป็นimplements Map<K,V>, Set<Map.Entry<K,V>>เช่นนั้นฉันก็มักจะเห็นด้วย .. ดูเหมือนเป็นเรื่องธรรมชาติ แต่นั่นไม่ได้ผลดีใช่มั้ย สมมติว่าเรามีHashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>ฯลฯ ... ว่าเป็นสิ่งที่ดี แต่ถ้าคุณมีentrySet()ไม่มีใครจะลืมที่จะใช้วิธีการนั้นและคุณสามารถมั่นใจได้ว่าคุณจะได้รับ entrySet สำหรับแผนที่ใด ๆ ในขณะที่คุณไม่ได้ถ้าคุณมีความหวังว่า ที่ผู้ดำเนินการได้ดำเนินการทั้งสองอินเตอร์เฟส ...

เหตุผลที่ฉันไม่ต้องการมีinterface Map<K,V> extends Set<Map.Entry<K,V>>เพียงเพราะจะมีวิธีการมากขึ้น และหลังจากทั้งหมดพวกเขาต่างกันใช่มั้ย นอกจากนี้ในทางปฏิบัติถ้าฉันกดmap.ใน IDE ฉันไม่ต้องการเห็น.remove(Object obj)และ.remove(Map.Entry<K,V> entry)เพราะฉันทำไม่ได้hit ctrl+space, r, returnและทำได้ด้วย


สำหรับฉันแล้วดูเหมือนว่าถ้าใครสามารถเรียกร้องความหมายว่า "แผนที่ตั้งอยู่บน Map.Entry" จากนั้นหนึ่งจะรบกวนการใช้วิธีการที่เกี่ยวข้อง; และถ้าใครไม่สามารถทำให้คำสั่งนั้นก็จะไม่ - นั่นคือควรจะกังวลเกี่ยวกับ
einpoklum

0

Map<K,V>ไม่ควรขยายSet<Map.Entry<K,V>>ตั้งแต่:

  • คุณไม่สามารถเพิ่มMap.Entrys ที่แตกต่างกันด้วยคีย์เดียวกันMapได้ แต่
  • คุณสามารถเพิ่มMap.Entrys ที่แตกต่างกันด้วยคีย์เดียวกันSet<Map.Entry>ลงไป

... นี่เป็นคำสั่งสั้น ๆ ของ ghist ในส่วนที่สองของคำตอบ @cletus
einpoklum

-2

ตรงและเรียบง่าย คอลเลกชันเป็นอินเทอร์เฟซที่คาดว่าจะมีเพียงวัตถุเดียวในขณะที่แผนที่ต้องการสอง

Collection(Object o);
Map<Object,Object>

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