ทำไม Java 8 ถึงไม่รวมคอลเลกชันที่ไม่เปลี่ยนรูป?


130

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

โดยไม่ต้องเปลี่ยนรหัสใด ๆ ที่มีอยู่ทีม Java สามารถเพิ่มอินเทอร์เฟซที่ไม่เปลี่ยนรูปได้ซึ่งเป็นอินเทอร์เฟซที่ไม่เปลี่ยนรูปได้ลบเมธอด "set" และทำให้อินเตอร์เฟสที่มีอยู่ขยายออกจากพวกเขาเช่นนี้

                  ImmutableIterable
     ____________/       |
    /                    |
Iterable        ImmutableCollection
   |    _______/    /          \   \___________
   |   /           /            \              \
 Collection  ImmutableList  ImmutableSet  ImmutableMap  ...
    \  \  \_________|______________|__________   |
     \  \___________|____________  |          \  |
      \___________  |            \ |           \ |
                  List            Set           Map ...

แน่นอนว่าการทำงานเช่น List.add () และ Map.put () จะส่งคืนบูลีนหรือค่าก่อนหน้าสำหรับคีย์ที่กำหนดเพื่อระบุว่าการดำเนินการสำเร็จหรือล้มเหลว คอลเลกชันที่ไม่เปลี่ยนรูปจะต้องปฏิบัติต่อวิธีการเช่นโรงงานและส่งคืนคอลเลกชันใหม่ที่มีองค์ประกอบที่เพิ่มเข้ามาซึ่งไม่สามารถใช้ได้กับลายเซ็นปัจจุบัน แต่นั่นอาจเป็นการหลีกเลี่ยงปัญหาโดยใช้ชื่อเมธอดอื่นเช่น ImmutableList.append () หรือ .addAt () และ ImmutableMap.putEntry () การใช้คำฟุ่มเฟื่อยที่เกิดขึ้นจะมากกว่าเมื่อเทียบกับผลประโยชน์ของการทำงานกับคอลเลกชันที่ไม่เปลี่ยนรูปแบบและระบบประเภทจะป้องกันข้อผิดพลาดของการโทรวิธีที่ผิด เมื่อเวลาผ่านไปวิธีการเก่าอาจเลิกใช้

ชนะคอลเลกชันที่ไม่เปลี่ยนรูป:

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

ในฐานะที่เป็นคนที่มีภาษาที่ชิมไม่ได้จึงยากที่จะกลับไปที่ Wild West ของการกลายพันธุ์ที่อาละวาด คอลเล็กชันของ Clojure (Abstraction ต่อเนื่อง) มีทุกอย่างที่คอลเลกชัน Java 8 มีให้รวมถึงการเปลี่ยนแปลงไม่ได้ (แม้ว่าอาจจะใช้หน่วยความจำเพิ่มเติมและเวลาเนื่องจากการซิงโครไนซ์เชื่อมโยงรายการแทนที่จะสตรีม) สกาล่ามีทั้งคอลเลคชั่นที่เปลี่ยนแปลงไม่ได้และไม่เปลี่ยนรูปแบบด้วยชุดปฏิบัติการเต็มรูปแบบและแม้ว่าการดำเนินการเหล่านั้นมีความกระตือรือร้นการเรียก .iterator ให้มุมมองที่ขี้เกียจ (และมีวิธีอื่น ๆ ฉันไม่เห็นว่า Java สามารถแข่งขันต่อไปได้อย่างไรหากไม่มีคอลเลกชันที่ไม่เปลี่ยนรูป

ใครสามารถชี้ให้ฉันดูประวัติศาสตร์หรือการสนทนาเกี่ยวกับเรื่องนี้ แน่นอนมันสาธารณะที่ไหนสักแห่ง


9
ที่เกี่ยวข้องกับเรื่องนี้ - Ayende บล็อกเมื่อเร็ว ๆ นี้เกี่ยวกับคอลเลกชันและคอลเลกชันที่ไม่เปลี่ยนรูปใน C # ด้วยมาตรฐาน ayende.com/blog/tags/performance - TL; DR - เปลี่ยนไม่ได้เป็นช้า
Oded

20
กับลำดับชั้นของคุณฉันสามารถให้ ImmutableList แก่คุณแล้วเปลี่ยนมันให้คุณเมื่อคุณไม่คาดหวังว่ามันจะทำลายสิ่งต่าง ๆ มากมายเช่นเดียวกับที่คุณมีconstคอลเล็กชัน
ratchet freak

18
@Oded Immutability ช้า แต่ก็ล็อคอยู่ ดังนั้นการรักษาประวัติ ความเรียบง่าย / ความถูกต้องเป็นความเร็วที่คุ้มค่าในหลาย ๆ สถานการณ์ ด้วยคอลเลกชันขนาดเล็กความเร็วไม่ใช่ปัญหา การวิเคราะห์ของ Ayende นั้นตั้งอยู่บนสมมติฐานที่ว่าคุณไม่ต้องการประวัติล็อคหรือความเรียบง่ายและคุณกำลังทำงานกับชุดข้อมูลขนาดใหญ่ บางครั้งมันเป็นเรื่องจริง แต่มันก็ไม่ใช่สิ่งที่ดีกว่าเสมอ มีการแลกเปลี่ยนกัน
GlenPeterson

5
@GlenPeterson เป็นสิ่งที่คัดลอกการป้องกันและCollections.unmodifiable*()มีไว้สำหรับ แต่ไม่ถือว่าสิ่งเหล่านี้ไม่เปลี่ยนรูปเมื่อพวกเขาไม่ใช่
วงล้อประหลาด

13
ใช่มั้ย? หากฟังก์ชั่นของคุณใช้ImmutableListในแผนภาพนั้นผู้คนสามารถผ่านในรูปแบบที่ไม่แน่นอนได้Listหรือไม่? ไม่นั่นเป็นการละเมิด LSP ที่แย่มาก
Telastyn

คำตอบ:


113

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

ยิ่งไปกว่านั้นคอลเลกชันที่ไม่เปลี่ยนรูปมักจะมีการใช้งานที่แตกต่างกันโดยพื้นฐานกว่าคู่ที่ไม่แน่นอน ลองพิจารณาตัวอย่างเช่นArrayListการเปลี่ยนArrayListไม่ได้ที่มีประสิทธิภาพจะไม่ได้เป็นอาร์เรย์เลย! มันควรจะดำเนินการกับต้นไม้ที่สมดุลและมีปัจจัยการแตกกิ่งใหญ่ Clojure ใช้ 32 IIRC การทำให้คอลเลกชันที่ไม่แน่นอนเป็น "ไม่เปลี่ยนรูป" โดยเพียงแค่เพิ่มการปรับปรุงการทำงานเป็นข้อบกพร่องด้านประสิทธิภาพเช่นเดียวกับหน่วยความจำรั่ว

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

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


21
ตัวอย่างของฉันพูดคุยเกี่ยวกับเปลี่ยนรูปอินเตอร์เฟซ Java สามารถให้ชุดเต็มของทั้งสองไม่แน่นอนและไม่เปลี่ยนรูปการใช้งานของอินเตอร์เฟซที่จะทำให้ที่จำเป็นในการแลกเปลี่ยน มันขึ้นอยู่กับโปรแกรมเมอร์ที่จะเลือกไม่แน่นอนหรือไม่เปลี่ยนรูปตามความเหมาะสม โปรแกรมเมอร์ต้องรู้ว่าเมื่อใดควรใช้ List vs. Set now โดยทั่วไปคุณไม่จำเป็นต้องมีเวอร์ชันที่ไม่แน่นอนจนกว่าคุณจะมีปัญหาด้านประสิทธิภาพและจากนั้นอาจจำเป็นในฐานะผู้สร้างเท่านั้น ไม่ว่าในกรณีใดการมีอินเทอร์เฟซที่ไม่เปลี่ยนแปลงจะเป็นชัยชนะของมัน
GlenPeterson

4
ฉันอ่านคำตอบของคุณอีกครั้งและฉันคิดว่าคุณกำลังพูดว่า Java มีข้อสันนิษฐานพื้นฐานเกี่ยวกับความไม่แน่นอน (เช่นถั่วถั่ว) และคอลเลกชันเป็นเพียงส่วนเล็ก ๆ ของภูเขาน้ำแข็งและการแกะสลักคำแนะนำนั้นจะไม่แก้ปัญหาพื้นฐาน จุดที่ถูกต้อง ฉันอาจยอมรับคำตอบนี้และเพิ่มความเร็วในการยอมรับ Scala ของฉัน! :-)
GlenPeterson

8
ฉันไม่แน่ใจว่าคอลเลกชันที่ไม่เปลี่ยนรูปนั้นต้องการความสามารถในการแบ่งปันชิ้นส่วนทั่วไปให้เป็นประโยชน์ ชนิดที่ไม่เปลี่ยนรูปแบบที่พบมากที่สุดใน Java ซึ่งเป็นคอลเลกชันที่ไม่เปลี่ยนรูปแบบของตัวละครใช้เพื่ออนุญาตการแบ่งปัน แต่ไม่ได้อีกต่อไป สิ่งสำคัญที่ทำให้มีประโยชน์คือความสามารถในการคัดลอกข้อมูลจาก a Stringไปยัง a StringBufferจัดการและจากนั้นคัดลอกข้อมูลไปยังที่ไม่เปลี่ยนรูปใหม่Stringได้อย่างรวดเร็ว การใช้รูปแบบดังกล่าวกับชุดและรายการอาจจะดีเท่ากับการใช้รูปแบบที่ไม่เปลี่ยนรูปแบบซึ่งได้รับการออกแบบมาเพื่ออำนวยความสะดวกในการผลิตอินสแตนซ์ที่เปลี่ยนแปลงเล็กน้อย แต่อาจจะดีกว่า ...
supercat

3
เป็นไปได้ทั้งหมดที่จะสร้างคอลเลกชันที่ไม่เปลี่ยนรูปแบบใน Java โดยใช้การแบ่งปัน รายการที่เก็บไว้ในคอลเลกชันคือการอ้างอิงและผู้อ้างอิงของพวกเขาอาจกลายพันธุ์ - ดังนั้นอะไร พฤติกรรมดังกล่าวได้แบ่งคอลเลกชันที่มีอยู่แล้วเช่น HashMap และ TreeSet แต่ก็มีการใช้งานใน Java และหากมีหลายคอลเลกชันที่มีการอ้างอิงไปยังวัตถุเดียวกันก็คาดว่าการปรับเปลี่ยนวัตถุจะทำให้เกิดการเปลี่ยนแปลงที่มองเห็นได้เมื่อมันดูจากคอลเลกชันทั้งหมด
ความลับของโซโลมอนอฟ

4
ตอนนี้, เป็นไปได้ทั้งหมดที่จะใช้คอลเลกชันไม่เปลี่ยนรูปที่มีประสิทธิภาพบน JVM ด้วยการแบ่งปันโครงสร้าง. Scala และ Clojure ให้พวกเขาเป็นส่วนหนึ่งของห้องสมุดมาตรฐานของพวกเขาการใช้งานทั้งสองขึ้นอยู่กับ HAMT ของ Phil Bagwell (Hash Array Mapped Trie Trie) คำสั่งของคุณเกี่ยวกับ Clojure การนำโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบมาใช้กับต้นไม้ BALANCED นั้นผิดทั้งหมด
sesm

77

คอลเลกชันที่ไม่แน่นอนไม่ได้เป็นประเภทย่อยของคอลเลกชันที่ไม่เปลี่ยนรูป คอลเลกชันที่เปลี่ยนแปลงไม่ได้และไม่เปลี่ยนรูปนั้นเป็นลูกหลานของพี่น้องของคอลเลกชันที่อ่านได้ น่าเสียดายแนวคิดของ "ที่อ่านได้", "อ่านอย่างเดียว" และ "ไม่เปลี่ยนรูป" ดูเหมือนจะไม่ชัดเจนแม้ว่าจะหมายถึงสามสิ่งที่แตกต่างกัน

  • คลาสฐานของคอลเลกชันที่อ่านได้หรือประเภทอินเตอร์เฟสนั้นสัญญาว่าใคร ๆ ก็สามารถอ่านไอเท็มได้และไม่ได้ให้วิธีการแก้ไขคอลเลกชันโดยตรง แต่ไม่รับประกันว่ารหัสที่ได้รับการอ้างอิงนั้นไม่สามารถส่งหรือจัดการมันได้

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

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

บางครั้งมันเป็นประโยชน์ที่จะมีอย่างยิ่งที่เกี่ยวข้องประเภทที่ไม่แน่นอนและไม่เปลี่ยนรูปคอลเลกชันที่ทั้งสองใช้หรือเป็นผลมาจากชนิดเดียวกัน "อ่าน" และจะมีชนิดที่สามารถอ่านได้ ได้แก่AsImmutable, AsMutableและAsNewMutableวิธีการ การออกแบบดังกล่าวสามารถอนุญาตให้โค้ดที่ต้องการเก็บข้อมูลในคอลเลกชันที่จะเรียกAsImmutable; วิธีการนั้นจะทำการคัดลอกแบบป้องกันหากการรวบรวมนั้นไม่แน่นอน แต่ข้ามการคัดลอกถ้ามันไม่เปลี่ยนรูป


1
คำตอบที่ดี คอลเลกชันที่ไม่เปลี่ยนรูปแบบสามารถให้การรับประกันที่แข็งแกร่งแก่คุณเกี่ยวกับความปลอดภัยของเธรดและวิธีที่คุณสามารถให้เหตุผลกับพวกเขาเมื่อเวลาผ่านไป คอลเลกชันอ่าน / อ่านอย่างเดียวไม่ได้ ในความเป็นจริงเพื่อเป็นเกียรติแก่หลักการ substition liskov, อ่านอย่างเดียวและไม่เปลี่ยนรูปควรจะเป็นประเภทฐานนามธรรมด้วยวิธีการขั้นสุดท้ายและสมาชิกส่วนตัวเพื่อให้แน่ใจว่าไม่มีชั้นเรียนที่ได้มาสามารถทำลายรับประกันได้ตามประเภท หรือควรเป็นประเภทที่เป็นรูปธรรมโดยสมบูรณ์ซึ่งอาจรวมถึงการรวบรวม (อ่านอย่างเดียว) หรือรับสำเนาการป้องกัน (ไม่เปลี่ยนรูป) เสมอ นี่คือวิธีที่ Guava ImmutableList ทำ
Laurent Bourgault-Roy

1
@ LaurentBourgault-Roy: มีข้อดีสำหรับทั้งชนิดที่ไม่เปลี่ยนรูปแบบที่ปิดสนิทและสืบทอดได้ หากไม่มีใครต้องการอนุญาตให้ชั้นเรียนที่ผิดกฎหมายสามารถทำลายค่าคงที่ได้ประเภทที่ผนึกสามารถเสนอการป้องกันในขณะที่ชั้นเรียนที่สืบทอดได้ไม่มี ในทางกลับกันมันอาจเป็นไปได้สำหรับรหัสที่รู้บางอย่างเกี่ยวกับข้อมูลที่เก็บไว้เพื่อจัดเก็บมากขึ้นกว่าประเภทที่ไม่รู้อะไรเลยเกี่ยวกับมัน พิจารณาตัวอย่างเช่นชนิด ReadableIndexedIntSequence ซึ่งห่อหุ้มลำดับของintด้วยวิธีการและgetLength() getItemAt(int)
supercat

1
@ LaurentBourgault-Roy: ให้ a ReadableIndexedIntSequenceสามารถสร้างอินสแตนซ์ของประเภทไม่เปลี่ยนรูปที่ได้รับการสนับสนุนโดยการคัดลอกรายการทั้งหมดลงในอาร์เรย์ แต่สมมติว่าการดำเนินการเฉพาะส่งกลับ 16777216 สำหรับความยาวและ((long)index*index)>>24แต่ละรายการ นั่นจะเป็นลำดับของจำนวนเต็มที่ไม่เปลี่ยนรูป แต่การคัดลอกไปยังอาเรย์จะเป็นการเสียเวลาและความทรงจำไปอย่างมาก
supercat

1
ฉันเห็นด้วยอย่างยิ่ง. โซลูชันของฉันให้ความถูกต้องแก่คุณ(แต่ไม่เกินจุด) แต่เพื่อให้ได้ประสิทธิภาพกับชุดข้อมูลขนาดใหญ่คุณต้องมีโครงสร้างและการออกแบบที่คงอยู่เพื่อความไม่สามารถเปลี่ยนแปลงได้ตั้งแต่ต้น สำหรับคอลเล็กชั่นเล็ก ๆ น้อย ๆ ที่คุณสามารถหลีกเลี่ยงได้กับการถ่ายสำเนาที่ไม่เปลี่ยนรูปแบบเป็นครั้งคราว ฉันจำได้ว่า Scala ทำการวิเคราะห์บางส่วนของโปรแกรมต่าง ๆ และพบว่าบางสิ่งเช่น 90% ของรายการที่เปิดให้มีความยาว 10 หรือน้อยกว่านั้น
Laurent Bourgault-Roy

1
@ LaurentBourgault-Roy: คำถามพื้นฐานคือว่าใครไว้ใจคนที่จะไม่ก่อให้เกิดการใช้งานที่ไม่สมบูรณ์หรือคลาสที่ได้รับมา ถ้ามีใครทำและถ้า interfaces / base class ให้ asMutable / asImmutable method มันอาจเป็นไปได้ที่จะปรับปรุงประสิทธิภาพโดยคำสั่งขนาดใหญ่ [เช่นเปรียบเทียบค่าใช้จ่ายในการเรียกasImmutableใช้อินสแตนซ์ของลำดับที่กำหนดไว้ข้างต้นกับต้นทุนการสร้าง การคัดลอกอาเรย์ที่ไม่เปลี่ยนรูป] ฉันจะวางตัวว่าการมีส่วนต่อประสานที่กำหนดไว้สำหรับวัตถุประสงค์ดังกล่าวน่าจะดีกว่าการพยายามใช้วิธีการเฉพาะกิจ IMHO เหตุผลที่ยิ่งใหญ่ที่สุด ...
supercat

15

Java Collections Framework ให้ความสามารถในการสร้างคอลเลกชันรุ่นอ่านอย่างเดียวโดยวิธีการคงที่หกวิธีในคลาส java.util.Collections :

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

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


4
การออกแบบคอลเลกชันที่ไม่สามารถแก้ไขได้จะได้รับการปรับปรุงอย่างมากหากเครื่องห่อหุ้มมีข้อบ่งชี้ว่าสิ่งที่ถูกห่อนั้นไม่เปลี่ยนรูปแล้วและมีimmutableListวิธีการอื่น ๆ จากโรงงานซึ่งจะส่งคืนเครื่องห่อหุ้มแบบอ่านอย่างเดียว รายการเว้นแต่ผ่านในรายการที่มีอยู่แล้วไม่เปลี่ยนรูป มันจะง่ายต่อการสร้างผู้ใช้กำหนดประเภทเช่นนั้น แต่สำหรับปัญหาที่หนึ่ง: จะมีวิธีการที่ไม่มีวิธีการที่จะรับรู้ว่ามันไม่ควรต้องคัดลอกวัตถุที่ส่งกลับโดยjoesCollections.immutableList fredsCollections.immutableList
supercat

8

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

ร้อยละของโปรแกรมเมอร์ Java ตระหนักถึงการดำรงอยู่ของunmodifiableCollection()ตระกูลของวิธีการCollectionsเรียน แต่แม้ในหมู่พวกเขาหลายคนก็ไม่ได้ใส่ใจกับมัน

และฉันไม่สามารถตำหนิพวกเขา: อินเทอร์เฟซที่แกล้งทำเป็นอ่าน - เขียน แต่จะโยนUnsupportedOperationExceptionถ้าคุณทำผิดพลาดในการเรียกใช้วิธีการ 'เขียน' ใด ๆ ของมันเป็นสิ่งที่ชั่วร้ายที่จะมี!

ตอนนี้อินเตอร์เฟซที่ชอบCollectionที่จะหายไปadd(), remove()และclear()วิธีการที่จะไม่เป็น "ImmutableCollection" อินเตอร์เฟซ; มันจะเป็นอินเตอร์เฟซ "UnmodifiableCollection" ตามความเป็นจริงไม่มีอินเทอร์เฟซ "ImmutableCollection" เนื่องจากการเปลี่ยนแปลงไม่ได้เป็นลักษณะของการนำไปใช้ไม่ใช่ลักษณะของอินเทอร์เฟซ ฉันรู้ว่ามันไม่ค่อยชัดเจน ให้ฉันอธิบาย

สมมติว่ามีใครบางคนส่งมอบคุณให้กับส่วนต่อประสานการอ่านอย่างเดียว ปลอดภัยไหมที่จะส่งต่อไปยังเธรดอื่น? หากคุณรู้ว่ามันหมายถึงคอลเลกชันที่ไม่เปลี่ยนรูปแบบอย่างแท้จริงคำตอบก็คือ "ใช่"; น่าเสียดายเนื่องจากเป็นอินเทอร์เฟซคุณไม่ทราบวิธีการใช้งานดังนั้นคำตอบต้องไม่ใช่ : สำหรับทุกสิ่งที่คุณรู้มันอาจเป็นมุมมองที่ไม่สามารถแก้ไขได้ (สำหรับคุณ) ของคอลเลกชัน (เช่นเดียวกับสิ่งที่คุณได้รับด้วยCollections.unmodifiableCollection()) ดังนั้นการพยายามอ่านจากมันในขณะที่เธรดอื่นกำลังแก้ไขมันจะส่งผลให้เกิดการอ่านข้อมูลที่เสียหาย

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

เพื่อให้มีคอลเลกชันที่ไม่เปลี่ยนรูปพวกเขาจะต้องเป็นคลาสไม่ใช่อินเทอร์เฟซ!

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

ดังนั้นเพื่อให้มีชุดของคอลเลกชันที่สมบูรณ์ใน java (หรือภาษาอื่น ๆ ที่จำเป็นในการประกาศ) เราจะต้องมีสิ่งต่อไปนี้:

  1. ชุดของอินเตอร์เฟสการรวบรวมที่ไม่สามารถแก้ไขได้

  2. ชุดของอินเตอร์เฟสการรวบรวมที่ไม่แน่นอนซึ่งขยายส่วนที่ไม่สามารถแก้ไขได้

  3. ชุดของคลาสคอลเลกชันที่ไม่แน่นอนที่ใช้อินเทอร์เฟซที่ไม่แน่นอนและโดยส่วนต่อขยายยังเป็นอินเตอร์เฟสที่ไม่สามารถแก้ไขได้

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

ฉันนำสิ่งที่กล่าวมาข้างต้นทั้งหมดมาใช้เพื่อความสนุกสนานและฉันใช้มันในโครงการและพวกเขาทำงานเหมือนมีเสน่ห์

เหตุผลที่พวกเขาไม่ได้เป็นส่วนหนึ่งของจาวาไทม์อาจเป็นเพราะมันคิดว่ามันจะซับซ้อนเกินไป / ยากเกินไป / ยากที่จะเข้าใจ

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


จุดที่ดี สองรายละเอียด: 1. คอลเลกชันที่ไม่เปลี่ยนรูปแบบต้องใช้ลายเซ็นของเมธอดบางอย่างโดยเฉพาะ (ใช้รายการเป็นตัวอย่าง): List<T> add(T t)- เมธอด "mutator" ทั้งหมดจะต้องส่งคืนคอลเลกชันใหม่ที่สะท้อนถึงการเปลี่ยนแปลง 2. ดีขึ้นหรือแย่ลงอินเตอร์เฟสมักแสดงสัญญานอกเหนือจากลายเซ็น Serializable เป็นหนึ่งในอินเตอร์เฟสดังกล่าว ในทำนองเดียวกันเทียบเคียงที่คุณต้องใช้อย่างถูกต้องของcompareTo()วิธีการทำงานอย่างถูกต้องและความนึกคิดเข้ากันได้กับและequals() hashCode()
GlenPeterson

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

การใช้งานของคุณเป็นแบบสาธารณะหรือไม่ ฉันน่าจะถามเมื่อหลายเดือนก่อน อย่างไรก็ตามเหมืองของฉันคือ: github.com/GlenKPeterson/UncleJim
GlenPeterson

4
Suppose someone hands you such a read-only collection interface; is it safe to pass it to another thread?สมมติว่ามีคนส่งตัวอย่างของอินเทอร์เฟซคอลเลกชันที่ไม่แน่นอน ปลอดภัยไหมที่จะเรียกใช้วิธีการใด ๆ คุณไม่รู้ว่าการติดตั้งใช้งานจะไม่วนซ้ำตลอดไปยกเว้นหรือไม่สนใจสัญญาของอินเทอร์เฟซทั้งหมด เหตุใดจึงมีสองมาตรฐานเป็นพิเศษสำหรับคอลเลกชันที่ไม่เปลี่ยนรูป?
Doval

1
IMHO การให้เหตุผลของคุณกับอินเทอร์เฟซที่ไม่แน่นอนนั้นผิด คุณสามารถเขียนการใช้งานอินเทอร์เฟซที่ไม่เปลี่ยนรูปไม่ได้และจากนั้นก็หยุด แน่ใจ แต่นั่นเป็นความผิดของคุณในขณะที่คุณกำลังละเมิดสัญญา แค่หยุดทำอย่างนั้น ไม่แตกต่างจากการแบ่งSortedSetย่อยโดยชุดย่อยที่มีการใช้งานที่ไม่สอดคล้อง Comparableหรือโดยผ่านไม่สอดคล้องกัน เกือบทุกอย่างสามารถถูกทำลายได้ถ้าคุณต้องการ ฉันเดาว่านั่นคือสิ่งที่ @Dal แปลว่า "สองมาตรฐาน"
maaartinus

2

คอลเลกชันที่ไม่เปลี่ยนรูปแบบสามารถเรียกซ้ำลึก ๆ ได้เมื่อเทียบกับแต่ละคนและไม่มีประสิทธิภาพอย่างไม่มีเหตุผลหากความเท่าเทียมกันของวัตถุคือโดยแฮ็กแฮ็ค นี่เรียกว่าป่าเมอคลี มันสามารถเป็นต่อคอลเลกชันหรือภายในบางส่วนของพวกเขาเช่นต้นไม้ AVL (ปรับสมดุลตัวเอง) สำหรับแผนที่ที่เรียงลำดับ

ยกเว้นว่าวัตถุจาวาทั้งหมดในคอลเลกชันเหล่านี้มีรหัสที่ไม่ซ้ำกันหรือ bitstring ที่จะแฮชคอลเลกชันไม่มีอะไรที่จะแฮชเพื่อตั้งชื่อตัวเองโดยเฉพาะ

ตัวอย่าง: บนแล็ปท็อป 4x1.6 กิกะเฮิร์ตของฉันฉันสามารถเรียกใช้ 200K sha256s ต่อวินาทีของขนาดที่เล็กที่สุดที่เหมาะกับวงจรแฮช 1 ครั้ง (สูงสุด 55 ไบต์) เมื่อเทียบกับ 500K HashMap ops หรือ 3M ops ใน hashtable ของ longs 200K / log (collectionSize) คอลเลกชันใหม่ต่อวินาทีนั้นเร็วพอสำหรับบางสิ่งที่ความสมบูรณ์ของข้อมูลและความยืดหยุ่นในระดับโลกที่ไม่ระบุชื่อเป็นสิ่งสำคัญ


-3

ประสิทธิภาพ. คอลเลกชันตามธรรมชาติของพวกเขาอาจมีขนาดใหญ่มาก การคัดลอกองค์ประกอบ 1,000 รายการไปยังโครงสร้างใหม่ด้วยองค์ประกอบ 1001 แทนที่จะเป็นการแทรกองค์ประกอบเดียวนั้นน่ากลัวเพียงธรรมดา

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

การเก็บรักษา ด้วยวัตถุที่ไม่เปลี่ยนรูปแบบในสภาพแวดล้อมแบบมัลติเธรดคุณสามารถจบสำเนาของวัตถุ "เดียวกัน" หลายสิบชุดที่จุดต่าง ๆ ของวงจรชีวิต ไม่สำคัญสำหรับวัตถุในปฏิทินหรือวันที่ แต่เมื่อมีการรวบรวม 10,000 วิดเจ็ตสิ่งนี้จะฆ่าคุณ


12
คอลเลกชันที่ไม่เปลี่ยนรูปจะต้องมีการคัดลอกหากคุณไม่สามารถแบ่งปันได้เนื่องจากความไม่แน่นอนที่แพร่หลายอย่าง Java การเกิดขึ้นพร้อมกันนั้นง่ายขึ้นด้วยคอลเลกชันที่ไม่เปลี่ยนรูปเพราะพวกเขาไม่ต้องการการล็อค และสำหรับการมองเห็นคุณสามารถมีการอ้างอิงที่ไม่แน่นอนกับคอลเลกชันที่ไม่เปลี่ยนรูปแบบ (ทั่วไปใน OCaml) ด้วยการแบ่งปันการอัปเดตอาจฟรี คุณอาจทำการจัดสรรแบบลอการิทึมมากกว่าโครงสร้างที่ไม่แน่นอน แต่ในการอัพเดต subobjects ที่หมดอายุจำนวนมากสามารถถูกปลดปล่อยได้ทันทีหรือนำกลับมาใช้ใหม่ดังนั้นคุณจึงไม่จำเป็นต้องมีหน่วยความจำสูงกว่า
Jon Purdy

4
ปัญหาที่คู่รัก คอลเลกชันใน Clojure และ Scala นั้นมีทั้งที่ไม่เปลี่ยนรูป แต่สนับสนุนการทำสำเนาที่มีน้ำหนักเบา การเพิ่มองค์ประกอบ 1001 หมายถึงการคัดลอกองค์ประกอบน้อยกว่า 33 องค์ประกอบรวมถึงการสร้างตัวชี้ใหม่สองสามตัว หากคุณแชร์คอลเล็กชันที่ไม่สามารถเปลี่ยนแปลงได้ในเธรดคุณมีปัญหาการซิงโครไนซ์ทุกชนิดเมื่อคุณเปลี่ยน การดำเนินการเช่น "remove ()" เป็นสิ่งที่น่าหวาดกลัว นอกจากนี้ยังสามารถสร้างคอลเลกชันที่ไม่เปลี่ยนรูปแบบจากนั้นคัดลอกหนึ่งครั้งไปยังเวอร์ชันที่ไม่เปลี่ยนรูปได้อย่างปลอดภัยเพื่อแชร์ข้ามเธรด
GlenPeterson

4
การใช้ภาวะพร้อมกันเป็นข้อโต้แย้งต่อการเปลี่ยนแปลงไม่ได้เป็นเรื่องปกติ ทำซ้ำเช่นกัน
Tom Hawtin - tackline

4
นิด ๆ หน่อย ๆ miffed เกี่ยวกับคะแนนลงที่นี่ OP ถามว่าทำไมพวกเขาถึงไม่ใช้คอลเลคชั่นที่ไม่เปลี่ยนรูปแบบและฉันให้คำตอบสำหรับคำถาม น่าจะเป็นคำตอบเดียวที่ยอมรับได้ในหมู่แฟชั่นที่ใส่ใจคือ "เพราะพวกเขาทำผิดพลาด" จริง ๆ แล้วฉันมีประสบการณ์บางอย่างเกี่ยวกับเรื่องนี้เพื่อ refactor ชิ้นใหญ่ของรหัสโดยใช้คลาส BigDecimal ที่ยอดเยี่ยมเป็นอย่างอื่นอย่างหมดจดเพราะ perfomance ไม่ดีเนื่องจากการเปลี่ยนไม่ได้ 512 ครั้งที่ใช้คู่บวก messing รอบเพื่อแก้ไขทศนิยม
James Anderson

3
@JamesAnderson: ปัญหาของฉันกับคำตอบของคุณ: "ประสิทธิภาพ" - คุณสามารถพูดได้ว่าคอลเลกชันที่ไม่เปลี่ยนรูปแบบในชีวิตจริงมักจะใช้รูปแบบของการแบ่งปันและนำมาใช้ซ้ำเพื่อหลีกเลี่ยงปัญหาที่คุณอธิบาย "Concurrency" - อาร์กิวเมนต์ทำให้เดือดร้อนลงไปที่ "ถ้าคุณต้องการความไม่แน่นอนแล้ววัตถุที่เปลี่ยนไม่ได้จะไม่ทำงาน" ฉันหมายความว่าหากมีความคิดใน "รุ่นล่าสุดของสิ่งเดียวกัน" แล้วบางสิ่งบางอย่างจำเป็นต้องกลายพันธุ์ทั้งสิ่งที่ตัวเองหรือสิ่งที่เป็นเจ้าของสิ่งที่ และใน "ที่เก็บข้อมูล" คุณดูเหมือนจะบอกว่าบางครั้งไม่ต้องการความไม่แน่นอน
jhominal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.