บางครั้งฉันได้ยินมาว่าด้วย generics Java ไม่ถูกต้อง (อ้างอิงที่ใกล้ที่สุดที่นี่ )
ให้อภัยในความไม่มีประสบการณ์ของฉัน แต่อะไรจะทำให้พวกเขาดีขึ้น?
บางครั้งฉันได้ยินมาว่าด้วย generics Java ไม่ถูกต้อง (อ้างอิงที่ใกล้ที่สุดที่นี่ )
ให้อภัยในความไม่มีประสบการณ์ของฉัน แต่อะไรจะทำให้พวกเขาดีขึ้น?
คำตอบ:
แย่:
List<byte>
ซึ่งได้รับการสนับสนุนจากbyte[]
ตัวอย่างและไม่จำเป็นต้องมีการชกมวย)ดี:
ปัญหาที่ใหญ่ที่สุดคือ Java generics เป็นสิ่งเดียวในการคอมไพล์และคุณสามารถล้มล้างได้ในเวลาทำงาน C # ได้รับการยกย่องเนื่องจากมีการตรวจสอบเวลาทำงานมากกว่า มีการสนทนาที่ดีมากในโพสต์นี้และลิงก์ไปยังการสนทนาอื่น ๆ
Class
วัตถุไปรอบ ๆ ได้
ปัญหาหลักคือ Java ไม่มี generics ในรันไทม์ เป็นคุณสมบัติเวลาคอมไพล์
เมื่อคุณสร้างคลาสทั่วไปใน Java พวกเขาใช้เมธอดที่เรียกว่า "Type Erasure" เพื่อลบประเภททั่วไปทั้งหมดออกจากคลาสและแทนที่ด้วย Object generics รุ่นสูงไมล์คือคอมไพเลอร์เพียงแค่แทรก casts ไปยังประเภททั่วไปที่ระบุเมื่อใดก็ตามที่ปรากฏในเนื้อความของวิธีการ
นี้มีข้อเสียมาก IMHO ที่ใหญ่ที่สุดอย่างหนึ่งคือคุณไม่สามารถใช้การสะท้อนเพื่อตรวจสอบประเภททั่วไปได้ ประเภทไม่ได้เป็นแบบทั่วไปในรหัสไบต์และด้วยเหตุนี้จึงไม่สามารถตรวจสอบเป็นข้อมูลทั่วไปได้
ภาพรวมความแตกต่างที่ยอดเยี่ยมที่นี่: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) นำไปสู่พฤติกรรมแปลก ๆ ตัวอย่างที่ดีที่สุดที่ฉันคิดได้คือ สมมติ:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
จากนั้นประกาศสองตัวแปร:
MyClass<T> m1 = ...
MyClass m2 = ...
โทรgetOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
ข้อที่สองมีอาร์กิวเมนต์ประเภททั่วไปที่คอมไพลเลอร์ถูกตัดออกเนื่องจากเป็นประเภทดิบ (หมายถึงประเภทที่กำหนดพารามิเตอร์ไม่ได้ให้มา) แม้ว่าจะไม่มีส่วนเกี่ยวข้องกับประเภทที่กำหนดพารามิเตอร์ก็ตาม
ฉันจะพูดถึงคำประกาศที่ฉันชอบจาก JDK:
public class Enum<T extends Enum<T>>
นอกเหนือจากการใช้สัญลักษณ์แทน (ซึ่งเป็นถุงผสม) ฉันคิดว่า. Net generics ดีกว่า
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
อาจดูเหมือนแปลก / ซ้ำซ้อนในตอนแรก แต่ก็น่าสนใจจริงๆอย่างน้อยก็อยู่ในข้อ จำกัด ของ Java / มันเป็นชื่อสามัญ Enums มีคงvalues()
วิธีการให้อาร์เรย์ขององค์ประกอบของพวกเขาพิมพ์เป็น enum ไม่และประเภทที่จะถูกกำหนดโดยพารามิเตอร์ทั่วไปซึ่งหมายความว่าคุณต้องการEnum
Enum<T>
แน่นอนว่าการพิมพ์เพียงทำให้รู้สึกในบริบทของชนิดนับและ enums ทั้งหมดเป็นคลาสย่อยดังนั้นคุณต้องการEnum
Enum<T extends Enum>
อย่างไรก็ตาม Java ไม่ชอบการผสมประเภทดิบกับ generics ดังนั้นEnum<T extends Enum<T>>
เพื่อความสม่ำเสมอ
ฉันจะโยนความคิดเห็นที่ขัดแย้งกันออกไป Generics ทำให้ภาษาซับซ้อนและทำให้โค้ดซับซ้อน ตัวอย่างเช่นสมมติว่าฉันมีแผนที่ที่แมปสตริงกับรายการสตริง ในสมัยก่อนฉันสามารถประกาศสิ่งนี้ได้ง่ายๆว่า
Map someMap;
ตอนนี้ฉันต้องประกาศเป็น
Map<String, List<String>> someMap;
และทุกครั้งที่ฉันผ่านมันไปในวิธีการบางอย่างฉันต้องพูดคำประกาศยาว ๆ นั้นซ้ำอีกครั้ง ในความคิดของฉันการพิมพ์เพิ่มเติมทั้งหมดนั้นทำให้นักพัฒนาเสียสมาธิและพาเขาออกจาก "โซน" นอกจากนี้เมื่อโค้ดเต็มไปด้วย cruft จำนวนมากบางครั้งมันก็ยากที่จะกลับมาหามันในภายหลังและร่อนผ่านส่วนทั้งหมดอย่างรวดเร็วเพื่อค้นหาตรรกะที่สำคัญ
Java มีชื่อเสียงที่ไม่ดีในการเป็นหนึ่งในภาษาที่ละเอียดที่สุดในการใช้งานทั่วไปและภาษาทั่วไปก็เพิ่มปัญหานั้น
แล้วคุณจะซื้ออะไรเพื่อความฟุ่มเฟื่อยที่มากเกินไป? คุณมีปัญหากี่ครั้งที่มีคนใส่จำนวนเต็มลงในคอลเลกชันที่ควรจะเก็บ Strings หรือที่ที่มีคนพยายามดึง String ออกจากคอลเลกชันของจำนวนเต็ม จากประสบการณ์ 10 ปีในการทำงานสร้างแอปพลิเคชัน Java เชิงพาณิชย์นี่ไม่เคยเป็นสาเหตุของข้อผิดพลาดขนาดใหญ่ ดังนั้นฉันไม่แน่ใจจริงๆว่าคุณได้รับอะไรจากการใช้คำฟุ่มเฟือยเพิ่มเติม มันทำให้ฉันกลายเป็นสัมภาระพิเศษของระบบราชการจริงๆ
ตอนนี้ฉันจะได้รับความขัดแย้งจริงๆ สิ่งที่ฉันเห็นว่าเป็นปัญหาใหญ่ที่สุดของคอลเลกชันใน Java 1.4 คือความจำเป็นในการพิมพ์แคสต์ทุกที่ ฉันมองว่าพิมพ์ดีดเหล่านั้นเป็นส่วนเสริมที่มีลักษณะละเอียดซึ่งมีปัญหาหลายอย่างเช่นเดียวกับยาสามัญ ตัวอย่างเช่นฉันไม่สามารถทำได้
List someList = someMap.get("some key");
ฉันต้องทำ
List someList = (List) someMap.get("some key");
เหตุผลก็คือ get () ส่งคืน Object ซึ่งเป็น supertype ของ List ดังนั้นจึงไม่สามารถทำการมอบหมายได้หากไม่มีตัวพิมพ์ ลองคิดดูอีกครั้งว่ากฎนั้นซื้อคุณได้มากแค่ไหน จากประสบการณ์ของฉันไม่มาก
ฉันคิดว่า Java น่าจะดีกว่าถ้า 1) ไม่ได้เพิ่ม generics แต่ 2) อนุญาตให้มีการส่งโดยนัยจาก supertype เป็น subtype แทน ปล่อยให้แคสต์ที่ไม่ถูกต้องถูกจับในรันไทม์ จากนั้นฉันก็มีความเรียบง่ายในการกำหนด
Map someMap;
และทำในภายหลัง
List someList = someMap.get("some key");
ปัญหาทั้งหมดจะหายไปและฉันไม่คิดว่าจะแนะนำแหล่งที่มาของจุดบกพร่องใหม่ ๆ ในโค้ดของฉัน
ผลข้างเคียงอีกประการหนึ่งของการคอมไพล์ไทม์และไม่รันไทม์คือคุณไม่สามารถเรียกคอนสตรัคเตอร์ของประเภททั่วไปได้ ดังนั้นคุณจึงไม่สามารถใช้มันเพื่อติดตั้งโรงงานทั่วไปได้ ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Java generics ได้รับการตรวจสอบความถูกต้องในเวลาคอมไพล์จากนั้นข้อมูลประเภททั้งหมดจะถูกลบออก (กระบวนการนี้เรียกว่าtype erasureดังนั้น generic List<Integer>
จะถูกลดขนาดเป็นraw typeไม่ใช่ generic List
ซึ่งสามารถมีอ็อบเจ็กต์ของคลาสที่กำหนดเองได้
ส่งผลให้สามารถแทรกอ็อบเจกต์ที่กำหนดเองลงในรายการขณะรันไทม์ได้และตอนนี้ยังไม่สามารถบอกได้ว่าประเภทใดถูกใช้เป็นพารามิเตอร์ทั่วไป ผลที่ตามมาคือ
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
ฉันหวังว่านี่จะเป็น wiki เพื่อที่ฉันจะได้เพิ่มให้คนอื่น ... แต่ ...
ปัญหา:
<
หรือไม่ขยาย MyObject >
[] แต่ฉันไม่ได้รับอนุญาต)การเพิกเฉยต่อความยุ่งเหยิงในการลบข้อมูลทั้งประเภททั่วไปตามที่ระบุก็ไม่ได้ผล
สิ่งนี้รวบรวม:
List<Integer> x = Collections.emptyList();
แต่นี่เป็นข้อผิดพลาดทางไวยากรณ์:
foo(Collections.emptyList());
โดย foo ถูกกำหนดให้เป็น:
void foo(List<Integer> x) { /* method body not important */ }
ดังนั้นการตรวจสอบประเภทนิพจน์ขึ้นอยู่กับว่าถูกกำหนดให้กับตัวแปรโลคัลหรือพารามิเตอร์จริงของการเรียกใช้เมธอด จะบ้าแค่ไหน?
การนำ generics เข้าสู่ Java เป็นงานที่ยากเนื่องจากสถาปนิกพยายามสร้างสมดุลระหว่างฟังก์ชันการทำงานการใช้งานง่ายและความเข้ากันได้แบบย้อนหลังกับรหัสเดิม ค่อนข้างคาดหวังว่าจะต้องมีการประนีประนอม
มีบางคนที่รู้สึกเช่นกันว่าการใช้ภาษาทั่วไปของ Java ทำให้ความซับซ้อนของภาษาเพิ่มขึ้นจนถึงระดับที่ยอมรับไม่ได้ (ดู " Generics ถือว่าเป็นอันตราย " ของ Ken Arnold ) คำถามที่พบบ่อยเกี่ยวกับ Genericsของ Angelika Langer ให้ความคิดที่ดีว่าสิ่งที่ซับซ้อนจะกลายเป็นอย่างไร
Java ไม่บังคับใช้ Generics ในขณะดำเนินการเฉพาะในเวลาคอมไพล์
ซึ่งหมายความว่าคุณสามารถทำสิ่งที่น่าสนใจได้เช่นการเพิ่มประเภทที่ไม่ถูกต้องลงในคอลเล็กชันทั่วไป
ถ้าคุณฟังJava Posse # 279 - บทสัมภาษณ์ของ Joe Darcy และ Alex Buckleyพวกเขาพูดถึงประเด็นนี้ นอกจากนี้ยังเชื่อมโยงไปยังบล็อกโพสต์ของ Neal Gafter ชื่อReified Generics สำหรับ Javaที่ระบุว่า:
หลายคนไม่พอใจกับข้อ จำกัด ที่เกิดจากวิธีใช้ generics ใน Java โดยเฉพาะอย่างยิ่งพวกเขาไม่พอใจที่ไม่ได้ reified พารามิเตอร์ประเภททั่วไป: ไม่พร้อมใช้งานในรันไทม์ Generics ถูกนำไปใช้โดยใช้การลบซึ่งพารามิเตอร์ประเภททั่วไปจะถูกลบออกเมื่อรันไทม์
บล็อกโพสต์นั้นอ้างถึงรายการที่เก่ากว่าส่วนPuzzling Through Erasure: answerซึ่งเน้นประเด็นเกี่ยวกับความเข้ากันได้ของการย้ายข้อมูลในข้อกำหนด
เป้าหมายคือเพื่อให้ความเข้ากันได้แบบย้อนหลังของทั้งซอร์สโค้ดและอ็อบเจ็กต์โค้ดและความเข้ากันได้ของการย้ายข้อมูล