เหตุใด Java Collections จึงไม่สามารถจัดเก็บประเภท Primitives ได้โดยตรง


123

คอลเลกชัน Java จัดเก็บเฉพาะ Objects ไม่ใช่ประเภทดั้งเดิม อย่างไรก็ตามเราสามารถจัดเก็บคลาส Wrapper ได้

ทำไมต้องมีข้อ จำกัด นี้


2
ข้อ จำกัด นี้จะดูดเมื่อคุณจัดการกับ primitives และต้องการใช้ Queues เพื่อส่งและอัตราการส่งของคุณเร็วมาก ฉันกำลังจัดการกับปัญหานั้นในขณะนี้เนื่องจากการทำ autoboxing ใช้เวลามากเกินไป
JPM

ในทางเทคนิคแล้วประเภทดั้งเดิมคือวัตถุ (อินสแตนซ์เดี่ยวของสิ่งนั้นที่แน่นอน) พวกเขาไม่ได้กำหนดโดย a classแทนที่จะเป็นโดย JVM คำสั่งint i = 1กำหนดตัวชี้ไปยังอินสแตนซ์ซิงเกิลตันของอ็อบเจ็กต์ซึ่งกำหนดintใน JVM โดยตั้งเป็นค่าที่1กำหนดไว้ที่ใดที่หนึ่งใน JVM ใช่พอยน์เตอร์ใน Java - นี่เป็นเพียงการแยกตัวออกจากคุณโดยการใช้ภาษา ไม่สามารถใช้ Primitives เป็น Generics ได้เนื่องจากภาษา predicates ประเภททั่วไปทั้งหมดต้องเป็น supertype Objectดังนั้นทำไมจึงA<?>ต้องรวบรวมในA<Object>ขณะทำงาน
Robert E Fry

1
@RobertEFry primitive types ไม่ใช่อ อบเจ็กต์ใน Java ดังนั้นทุกสิ่งที่คุณเขียนเกี่ยวกับอินสแตนซ์แบบซิงเกิลตันจึงผิดและสับสนโดยพื้นฐาน ฉันขอแนะนำให้อ่านบท"ประเภทค่าและตัวแปร"ของข้อกำหนดภาษา Java ซึ่งกำหนดว่าวัตถุคืออะไร: "วัตถุ (§4.3.1) คืออินสแตนซ์ที่สร้างขึ้นแบบไดนามิกของประเภทคลาสหรืออาร์เรย์ที่สร้างขึ้นแบบไดนามิก "
typeracer

คำตอบ:


99

เป็นการตัดสินใจออกแบบ Java และบางคนคิดว่าผิดพลาด คอนเทนเนอร์ต้องการ Objects และ primitives ไม่ได้มาจาก Object

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

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

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

คำอธิบายที่ดีเกี่ยวกับ SO เกี่ยวกับการชกมวย: เหตุใดบางภาษาจึงต้องใช้ Boxing และ Unboxing

และคำวิจารณ์เกี่ยวกับ Java generics: ทำไมบางคนถึงอ้างว่าการใช้งานทั่วไปของ Java ไม่ดี?

ในการป้องกันของ Java เป็นเรื่องง่ายที่จะมองย้อนกลับไปและวิพากษ์วิจารณ์ JVM ได้ทนต่อการทดสอบของเวลาและเป็นการออกแบบที่ดีในหลาย ๆ ด้าน


6
ไม่ใช่ความผิดพลาดการแลกเปลี่ยนที่ได้รับการคัดเลือกมาอย่างดีซึ่งฉันเชื่อว่าได้ให้บริการ Java เป็นอย่างดีแน่นอน
DJClayworth

13
เป็นความผิดพลาดมากพอที่. NET ได้เรียนรู้จากมันและใช้งาน autoboxing ตั้งแต่เริ่มต้นและ generics ในระดับ VM โดยไม่มีค่าใช้จ่ายในการชกมวย ความพยายามของ Java ในการแก้ไขเป็นเพียงโซลูชันระดับไวยากรณ์ที่ยังคงได้รับผลกระทบจากประสิทธิภาพของ autoboxing เทียบกับมวยไม่ได้เลย การนำไปใช้งานของ Java แสดงให้เห็นถึงประสิทธิภาพที่ไม่ดีกับโครงสร้างข้อมูลขนาดใหญ่
codenheim

2
@mrjoltcola: IMHO การทำกล่องอัตโนมัติเริ่มต้นเป็นข้อผิดพลาด แต่ควรมีวิธีติดแท็กตัวแปรและพารามิเตอร์ซึ่งควรกำหนดค่ากล่องให้โดยอัตโนมัติ ถึงแม้ตอนนี้ฉันคิดว่าควรมีการเพิ่มวิธีการเพื่อระบุว่าตัวแปรหรือพารามิเตอร์บางตัวไม่ควรยอมรับค่าที่เพิ่งบรรจุกล่องอัตโนมัติ [เช่นควรถูกกฎหมายในการส่งผ่านObject.ReferenceEqualsการอ้างอิงประเภทObjectที่ระบุจำนวนเต็มแบบบรรจุกล่อง แต่ไม่ควรถูกกฎหมาย ส่งผ่านค่าจำนวนเต็ม] การแกะกล่องอัตโนมัติของ Java เป็น IMHO ที่น่ารังเกียจ
supercat

18

ทำให้การใช้งานง่ายขึ้น เนื่องจาก Java primitives ไม่ถือว่าเป็น Objects คุณจึงจำเป็นต้องสร้างคลาสคอลเลกชันแยกต่างหากสำหรับแต่ละ primitives เหล่านี้ (ไม่มีรหัสเทมเพลตที่จะแชร์)

คุณสามารถทำได้แน่นอนเพียงแค่ดูGNU Trove , Apache Commons PrimitivesหรือHPPC HPPC

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


11

มันเป็นการรวมกันของสองข้อเท็จจริง:

  • ประเภทดั้งเดิมของ Java ไม่ใช่ประเภทอ้างอิง (เช่น an intไม่ใช่ไฟล์Object )
  • Java ทำการ generics โดยใช้ type-erasure ของประเภทการอ้างอิง (เช่น a List<?>เป็นList<Object>รันไทม์จริงๆ)

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

สิ่งนี้สามารถหลีกเลี่ยงได้หรือไม่? บางที

  • ถ้าintเป็นไฟล์Objectก็ไม่จำเป็นต้องมีประเภทกล่องเลย
  • หากยาชื่อสามัญไม่ได้ทำโดยใช้ type-erasure อาจมีการใช้ primitives สำหรับพารามิเตอร์ type

8

มีแนวคิดของการชกมวยอัตโนมัติและการแกะกล่องอัตโนมัติ หากคุณพยายามจัดเก็บintในList<Integer>คอมไพลเลอร์ Java จะแปลงเป็นIntegerไฟล์.


1
Autoboxing เปิดตัวใน Java 1.5 พร้อมกับ Generics
Jeremy

1
แต่มันเป็นสิ่งที่รวบรวมเวลา; วากยสัมพันธ์น้ำตาลโดยไม่มีประโยชน์ด้านประสิทธิภาพ ออโตบ็อกซ์คอมไพเลอร์ Java ดังนั้นการลงโทษด้านประสิทธิภาพเมื่อเทียบกับการใช้งาน VM เช่น. NET ที่ชื่อสามัญไม่เกี่ยวข้องกับการชกมวย
codenheim

1
@mrjoltcola: ประเด็นของคุณคืออะไร? ฉันแค่แบ่งปันข้อเท็จจริงไม่ใช่โต้แย้ง
Jeremy

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

2

มันไม่ใช่ข้อ จำกัด จริงๆเหรอ?

พิจารณาว่าคุณต้องการสร้างคอลเลกชันที่เก็บค่าดั้งเดิมไว้หรือไม่ คุณจะเขียนคอลเลกชันที่สามารถจัดเก็บ int หรือ float หรือ char ได้อย่างไร เป็นไปได้มากว่าคุณจะได้รับคอลเลกชันมากมายดังนั้นคุณจะต้องมี Intlist และ charlist เป็นต้น

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


7
"คุณจะเขียนคอลเลกชันที่เก็บ int หรือ float หรือ char ได้อย่างไร" - ด้วยการใช้งานทั่วไป / เทมเพลตเช่นภาษาอื่น ๆ ที่ไม่ต้องเสียค่าปรับจากการแสร้งทำเป็นว่าทุกสิ่งทุกอย่างเป็นวัตถุ
codenheim

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

2
@DJClayworth จะใช้ได้ดีก็ต่อเมื่อค่าคีย์ไม่กระจัดกระจาย แน่นอนคุณสามารถใช้อาร์เรย์เสริมเพื่อติดตามคีย์ได้ แต่นั่นก็มีปัญหาในตัวเอง: การเข้าถึงที่ค่อนข้างมีประสิทธิภาพจะต้องจัดเรียงอาร์เรย์ทั้งสองตามลำดับคีย์เพื่อให้สามารถค้นหาแบบไบนารีได้ซึ่งจะทำการแทรกและลบ ไม่มีประสิทธิภาพเว้นแต่ว่าการแทรก / การลบจะมีรูปแบบเช่นนั้นรายการที่แทรกมีแนวโน้มที่จะลงเอยด้วยการที่รายการที่ถูกลบไปก่อนหน้านี้และ / หรือบัฟเฟอร์บางส่วนถูกสลับกันในอาร์เรย์เป็นต้นมีทรัพยากรที่พร้อมใช้งาน แต่ก็เป็นการดีที่จะมีใน Java นั่นเอง
JAB

@JAB จริงๆแล้วมันใช้งานได้ดีถ้าคีย์เบาบางเพียงแค่ต้องการหน่วยความจำมากกว่าถ้าคีย์ไม่เบาบาง และถ้าคีย์เหล่านั้นเบาบางแสดงว่ามีไม่มากนักและการใช้ Integer เป็นคีย์ก็ใช้ได้ดี ใช้วิธีใดก็ตามที่ต้องการความจำน้อยที่สุด หรือแบบไหนก็ได้ถ้าไม่สนใจ.
DJClayworth

0

ฉันคิดว่าเราอาจเห็นความคืบหน้าในพื้นที่นี้ใน JDK อาจเป็นไปได้ใน Java 10 ตาม JEP นี้ - http://openjdk.java.net/jeps/218 http://openjdk.java.net/jeps/218

หากคุณต้องการหลีกเลี่ยงการชกมวยแบบดั้งเดิมในคอลเลกชั่นวันนี้มีทางเลือกของบุคคลที่สามหลายทาง นอกจากตัวเลือกของบุคคลที่สามที่กล่าวถึงก่อนหน้านี้แล้วยังมีEclipse Collections , FastUtilและKoloboke Koloboke

มีการเผยแพร่การเปรียบเทียบแผนที่ดั้งเดิมเมื่อไม่นานมานี้ด้วยชื่อ: ภาพรวม HashMap ขนาดใหญ่: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove ไลบรารี GS Collections (Goldman Sachs) ถูกย้ายไปยัง Eclipse Foundation และตอนนี้เป็น Eclipse Collections


0

เหตุผลหลักคือกลยุทธ์การออกแบบ java ++ 1) คอลเลกชันต้องการอ็อบเจ็กต์สำหรับการจัดการและดั้งเดิมไม่ได้มาจากอ็อบเจ็กต์ดังนั้นนี่อาจเป็นเหตุผลอื่น 2) ชนิดข้อมูลดั้งเดิมของ Java ไม่ใช่ประเภทอ้างอิงสำหรับเช่น int ไม่ใช่วัตถุ

เพื่อเอาชนะ: -

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

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