การพิมพ์ย่อยไม่แปรเปลี่ยนสำหรับชนิดที่กำหนดพารามิเตอร์ แม้ยากชั้นDog
เป็นชนิดย่อยของAnimal
ชนิดแปรไม่ได้เป็นชนิดย่อยของList<Dog>
List<Animal>
ในทางตรงกันข้ามcovariant subtyping จะถูกใช้โดยอาร์เรย์ดังนั้นชนิดอาร์เรย์เป็นชนิดย่อยของDog[]
Animal[]
ประเภทย่อยที่ไม่แปรเปลี่ยนทำให้แน่ใจได้ว่าข้อ จำกัด ประเภทที่บังคับใช้โดย Java จะไม่ถูกละเมิด พิจารณารหัสต่อไปนี้ที่ได้รับจาก @Jon Skeet:
List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);
ตามที่ระบุไว้โดย @Jon Skeet รหัสนี้ผิดกฎหมายเพราะไม่เช่นนั้นจะเป็นการละเมิดข้อ จำกัด ประเภทโดยส่งคืนแมวเมื่อสุนัขคาดหวัง
มันเป็นคำแนะนำในการเปรียบเทียบรหัสข้างต้นกับอะนาล็อกสำหรับอาร์เรย์
Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];
รหัสถูกกฎหมาย อย่างไรก็ตามพ่นยกเว้นการจัดเก็บอาร์เรย์ อาร์เรย์ดำเนินการชนิดของมันในเวลาทำงานด้วยวิธีนี้ JVM สามารถบังคับใช้ความปลอดภัยของประเภทของการพิมพ์ย่อย covariant
หากต้องการทำความเข้าใจเพิ่มเติมให้ดูที่โค้ดไบต์ที่สร้างโดยjavap
คลาสด้านล่าง:
import java.util.ArrayList;
import java.util.List;
public class Demonstration {
public void normal() {
List normal = new ArrayList(1);
normal.add("lorem ipsum");
}
public void parameterized() {
List<String> parameterized = new ArrayList<>(1);
parameterized.add("lorem ipsum");
}
}
การใช้คำสั่งjavap -c Demonstration
จะแสดง Java bytecode ต่อไปนี้:
Compiled from "Demonstration.java"
public class Demonstration {
public Demonstration();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void normal();
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: iconst_1
5: invokespecial #3 // Method java/util/ArrayList."<init>":(I)V
8: astore_1
9: aload_1
10: ldc #4 // String lorem ipsum
12: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
17: pop
18: return
public void parameterized();
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: iconst_1
5: invokespecial #3 // Method java/util/ArrayList."<init>":(I)V
8: astore_1
9: aload_1
10: ldc #4 // String lorem ipsum
12: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
17: pop
18: return
}
สังเกตว่ารหัสของวิธีเนื้อความที่แปลนั้นเหมือนกัน คอมไพเลอร์แทนที่แต่ละประเภทพารามิเตอร์โดยการลบของมันลบออกคุณสมบัตินี้มีความสำคัญอย่างยิ่งที่จะไม่ทำลายความเข้ากันได้ย้อนหลัง
โดยสรุปความปลอดภัยแบบรันไทม์เป็นไปไม่ได้สำหรับประเภทที่กำหนดพารามิเตอร์เนื่องจากคอมไพเลอร์แทนที่แต่ละประเภทพารามิเตอร์โดยการลบออก สิ่งนี้ทำให้ประเภทที่กำหนดพารามิเตอร์ไม่มีอะไรมากไปกว่าน้ำตาลทราย