เหตุใด generics ใน Java จึงทำงานกับคลาสได้ แต่ไม่ใช่ชนิดพื้นฐาน
ตัวอย่างเช่นวิธีนี้ใช้งานได้ดี:
List<Integer> foo = new ArrayList<Integer>();
แต่สิ่งนี้ไม่ได้รับอนุญาต:
List<int> bar = new ArrayList<int>();
เหตุใด generics ใน Java จึงทำงานกับคลาสได้ แต่ไม่ใช่ชนิดพื้นฐาน
ตัวอย่างเช่นวิธีนี้ใช้งานได้ดี:
List<Integer> foo = new ArrayList<Integer>();
แต่สิ่งนี้ไม่ได้รับอนุญาต:
List<int> bar = new ArrayList<int>();
คำตอบ:
Generics ใน Java เป็นคอมไพล์เวลาคอมไพล์ทั้งหมดคอมไพเลอร์จะเปลี่ยนการใช้งานทั่วไปทั้งหมดให้กลายเป็น casts ให้เป็นประเภทที่ถูกต้อง นี่คือการรักษาความเข้ากันได้ย้อนหลังกับเวลา JVM ก่อนหน้า
นี้:
List<ClassA> list = new ArrayList<ClassA>();
list.add(new ClassA());
ClassA a = list.get(0);
กลายเป็น (ประมาณ):
List list = new ArrayList();
list.add(new ClassA());
ClassA a = (ClassA)list.get(0);
ดังนั้นสิ่งใดก็ตามที่ใช้เป็น generics จะต้องสามารถแปลงเป็น Object ได้ (ในตัวอย่างนี้get(0)
คืนค่าเป็น an Object
) และประเภทดั้งเดิมจะไม่ ดังนั้นจึงไม่สามารถใช้ในยาชื่อสามัญได้
ใน Java generics วิธีการทำงานที่พวกเขาทำ ... อย่างน้อยในส่วน ... เพราะพวกเขามีการเพิ่มภาษาที่ตัวเลขของปีหลังจากภาษาที่ได้รับการออกแบบ1 ออกแบบภาษาถูกจำกัดในตัวเลือกของพวกเขาสำหรับข้อมูลทั่วไปโดยมีที่จะเกิดขึ้นกับการออกแบบที่เป็นที่เข้ากันได้กับภาษาที่มีอยู่และห้องสมุดชั้น Java
ภาษาการเขียนโปรแกรมอื่น ๆ (เช่น C ++, C #, Ada) อนุญาตให้ใช้ประเภทดั้งเดิมเพื่อเป็นประเภทพารามิเตอร์สำหรับ generics แต่ด้านพลิกของการทำเช่นนี้คือการใช้งานของ generics (หรือประเภทแม่แบบ) ภาษาดังกล่าวมักจะนำไปสู่การสร้างสำเนาทั่วไปประเภทที่แตกต่างกันสำหรับการกำหนดพารามิเตอร์แต่ละประเภท
1 - เหตุผลทั่วไปที่ไม่รวมอยู่ใน Java 1.0 เป็นเพราะความกดดันด้านเวลา พวกเขารู้สึกว่าต้องปล่อยภาษาจาวาออกมาอย่างรวดเร็วเพื่อเติมเต็มโอกาสทางการตลาดใหม่ที่นำเสนอโดยเว็บเบราว์เซอร์ James Gosling แจ้งว่าเขาจะชอบใส่ชื่อสามัญถ้าพวกเขามีเวลา สิ่งที่ภาษาจาวาจะมีลักษณะเช่นนี้ถ้าเกิดขึ้นก็คือทุกคนเดา
ในจาวา generics จะดำเนินการโดยใช้ "การลบประเภท" เพื่อความเข้ากันได้ย้อนหลัง ประเภททั่วไปทั้งหมดจะถูกแปลงเป็น Object ที่รันไทม์ ตัวอย่างเช่น,
public class Container<T> {
private T data;
public T getData() {
return data;
}
}
จะเห็นที่รันไทม์เป็น
public class Container {
private Object data;
public Object getData() {
return data;
}
}
คอมไพเลอร์มีหน้าที่จัดหานักแสดงที่เหมาะสมเพื่อความปลอดภัยของประเภท
Container<Integer> val = new Container<Integer>();
Integer data = val.getData()
จะกลายเป็น
Container val = new Container();
Integer data = (Integer) val.getData()
ตอนนี้คำถามคือทำไมเลือก "Object" เป็นชนิดที่รันไทม์?
Answer is Objectคือ superclass ของวัตถุทั้งหมดและสามารถแสดงวัตถุใด ๆ ที่ผู้ใช้กำหนด
ตั้งแต่ดั้งเดิมทั้งหมดไม่ได้รับมาจาก " Object " ดังนั้นเราจึงไม่สามารถใช้มันเป็นประเภททั่วไปได้
FYI: โครงการ Valhalla พยายามแก้ไขปัญหาดังกล่าวข้างต้น
java.lang.Object
คอลเลกชันจะมีการกำหนดที่จะต้องใช้ชนิดที่เกิดขึ้นจาก basetypes ไม่ทำอย่างนั้น
ตามเอกสารของ Javaตัวแปรชนิดทั่วไปสามารถสร้างอินสแตนซ์ได้ด้วยประเภทการอ้างอิงไม่ใช่ประเภทดั้งเดิม
นี้ควรจะมาใน Java 10 ภายใต้โครงการ Valhalla
ในหนังสือพิมพ์Brian Goetzเรื่องState of the Specialisation
มีคำอธิบายที่ยอดเยี่ยมเกี่ยวกับเหตุผลที่ไม่สนับสนุนทั่วไปสำหรับดั้งเดิม และจะนำไปใช้อย่างไรใน Java รุ่นใหม่ในอนาคต
การนำไปใช้ในปัจจุบันของ Java ถูกลบซึ่งสร้างคลาสหนึ่งสำหรับอินสแตนซ์การอ้างอิงทั้งหมดและไม่สนับสนุนอินสแตนซ์ดั้งเดิม (นี่คือการแปลที่เป็นเนื้อเดียวกันและข้อ จำกัด ที่ generics ของ Java สามารถทำได้เฉพาะประเภทการอ้างอิงนั้นมาจากข้อ จำกัด ของการแปลแบบเอกพันธ์ที่เกี่ยวข้องกับชุด bytecode ของ JVM ซึ่งใช้ bytecodes ที่แตกต่างกันสำหรับการดำเนินการกับประเภทการอ้างอิง อย่างไรก็ตามข้อมูลทั่วไปที่ถูกลบใน Java ให้ทั้งพารามิเตอร์เชิงพฤติกรรม (วิธีการทั่วไป) และข้อมูลพารามิเตอร์ (การสร้างอินสแตนซ์ดิบและไวด์การ์ดในประเภททั่วไป)
...
เลือกกลยุทธ์การแปลที่เป็นเนื้อเดียวกันโดยที่ตัวแปรประเภททั่วไปจะถูกลบไปที่ขอบเขตของมัน ซึ่งหมายความว่าไม่ว่าจะเป็นคลาสทั่วไปหรือไม่ก็ยังคงรวบรวมเป็นชั้นเดียวที่มีชื่อเดียวกันและมีลายเซ็นของสมาชิกที่เหมือนกัน ความปลอดภัยของประเภทได้รับการตรวจสอบในเวลารวบรวมและรันไทม์ไม่ได้ถูกเผยแพร่โดยระบบประเภททั่วไป ในทางกลับกันสิ่งนี้ได้กำหนดข้อ จำกัด ที่ generics สามารถทำงานได้มากกว่าประเภทการอ้างอิงเท่านั้นเนื่องจาก Object เป็นชนิดทั่วไปส่วนใหญ่ที่มีอยู่และไม่ขยายไปถึงประเภทดั้งเดิม
เมื่อสร้างวัตถุคุณไม่สามารถแทนที่ชนิดดั้งเดิมสำหรับพารามิเตอร์ชนิดได้เนื่องจากสาเหตุของข้อ จำกัด นี้เป็นปัญหาการใช้งานคอมไพเลอร์ ประเภทดั้งเดิมมีคำแนะนำแบบไบต์โค้ดสำหรับการโหลดและจัดเก็บลงในสแต็กเครื่องเสมือน ดังนั้นจึงเป็นไปไม่ได้ที่จะรวบรวมยาชื่อสามัญดั้งเดิมในเส้นทางไบต์ที่แยกจากกันเหล่านี้ แต่มันจะทำให้คอมไพเลอร์ซับซ้อน