ทำไม Java Generics ไม่สนับสนุนประเภทดั้งเดิม


236

เหตุใด generics ใน Java จึงทำงานกับคลาสได้ แต่ไม่ใช่ชนิดพื้นฐาน

ตัวอย่างเช่นวิธีนี้ใช้งานได้ดี:

List<Integer> foo = new ArrayList<Integer>();

แต่สิ่งนี้ไม่ได้รับอนุญาต:

List<int> bar = new ArrayList<int>();

1
int i = (int) วัตถุใหม่ (); รวบรวมได้ดีแม้ว่า
Sachin Verma

คำตอบ:


243

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) และประเภทดั้งเดิมจะไม่ ดังนั้นจึงไม่สามารถใช้ในยาชื่อสามัญได้


8
@DanyalAytekin - อันที่จริงแล้ว Java generics ไม่ได้รับการจัดการเหมือนแม่แบบ C ++ เลย ...
Stephen C

20
เหตุใดคอมไพเลอร์ Java จึงไม่สามารถพิมพ์ประเภทดั้งเดิมก่อนที่จะใช้งานได้ ควรเป็นไปได้ใช่มั้ย
vrwim

13
@vrwim - อาจเป็นไปได้ แต่มันก็แค่น้ำตาลซินแทคติค ปัญหาที่แท้จริงคือ Java generics ที่มีกล่องพื้นฐานมีราคาค่อนข้างแพงทั้งเวลาและพื้นที่เมื่อเทียบกับรุ่น C ++ / C # ... ซึ่งมีการใช้ประเภทดั้งเดิมจริง
Stephen C

6
@ MauganRa ใช่ฉันรู้ว่าฉันทำได้ :) ฉันยืนอยู่กับพื้นดินว่านี่คือการออกแบบที่แย่มาก หวังว่ามันจะได้รับการแก้ไขใน java 10 (หรือดังนั้นฉันได้ยิน) และฟังก์ชั่นการสั่งซื้อที่สูงขึ้น อย่าอ้างฉันในสรรพสินค้าใหญ่
Ced

4
@Ced เห็นด้วยอย่างยิ่งกับการออกแบบที่ไม่ดีซึ่งทำให้ผู้เริ่มต้นได้รับบาดเจ็บอย่างไม่รู้จบ
MauganRa

37

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

ภาษาการเขียนโปรแกรมอื่น ๆ (เช่น C ++, C #, Ada) อนุญาตให้ใช้ประเภทดั้งเดิมเพื่อเป็นประเภทพารามิเตอร์สำหรับ generics แต่ด้านพลิกของการทำเช่นนี้คือการใช้งานของ generics (หรือประเภทแม่แบบ) ภาษาดังกล่าวมักจะนำไปสู่การสร้างสำเนาทั่วไปประเภทที่แตกต่างกันสำหรับการกำหนดพารามิเตอร์แต่ละประเภท


1 - เหตุผลทั่วไปที่ไม่รวมอยู่ใน Java 1.0 เป็นเพราะความกดดันด้านเวลา พวกเขารู้สึกว่าต้องปล่อยภาษาจาวาออกมาอย่างรวดเร็วเพื่อเติมเต็มโอกาสทางการตลาดใหม่ที่นำเสนอโดยเว็บเบราว์เซอร์ James Gosling แจ้งว่าเขาจะชอบใส่ชื่อสามัญถ้าพวกเขามีเวลา สิ่งที่ภาษาจาวาจะมีลักษณะเช่นนี้ถ้าเกิดขึ้นก็คือทุกคนเดา


11

ในจาวา 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 พยายามแก้ไขปัญหาดังกล่าวข้างต้น


บวก 1 สำหรับการตั้งชื่อที่เหมาะสม
Drazen Bjelovuk

7

java.lang.Objectคอลเลกชันจะมีการกำหนดที่จะต้องใช้ชนิดที่เกิดขึ้นจาก basetypes ไม่ทำอย่างนั้น


26
ฉันคิดว่าคำถามที่นี่คือ "ทำไม" ทำไมข้อมูลทั่วไปถึงต้องการ Objects ฉันทามติดูเหมือนจะเป็นเพราะตัวเลือกการออกแบบน้อยลงและพังมากขึ้นในการเข้ากันได้ย้อนหลัง ในสายตาของฉันถ้ายาชื่อสามัญไม่สามารถจัดการกับสิ่งดั้งเดิมนั่นคือการขาดการทำงาน ตามที่กล่าวมาทุกอย่างที่เกี่ยวกับดึกดำบรรพ์จะต้องถูกเขียนสำหรับแต่ละดึกดำบรรพ์: แทนที่จะเป็น Comparator <t, t> เรามี Integer.compare (int a, int b), Byte.compare (byte a, byte b) เป็นต้น นั่นไม่ใช่ทางออก!
John P

1
ใช่ข้อมูลทั่วไปเกี่ยวกับประเภทดั้งเดิมจะเป็นคุณสมบัติที่ต้องมี นี่คือลิงค์ไปสู่ข้อเสนอสำหรับopenjdk.java.net/jeps/218
อีกา

5

ตามเอกสารของ Javaตัวแปรชนิดทั่วไปสามารถสร้างอินสแตนซ์ได้ด้วยประเภทการอ้างอิงไม่ใช่ประเภทดั้งเดิม

นี้ควรจะมาใน Java 10 ภายใต้โครงการ Valhalla

ในหนังสือพิมพ์Brian Goetzเรื่องState of the Specialisation

มีคำอธิบายที่ยอดเยี่ยมเกี่ยวกับเหตุผลที่ไม่สนับสนุนทั่วไปสำหรับดั้งเดิม และจะนำไปใช้อย่างไรใน Java รุ่นใหม่ในอนาคต

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

...

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


0

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

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