อาร์เรย์มีความแปรปรวน
อาร์เรย์จะกล่าวว่าเป็น covariant ซึ่งโดยทั่วไปหมายความว่าให้กฎ subtyping ชวาอาร์เรย์ของชนิดT[]
อาจมีองค์ประกอบของประเภทหรือชนิดย่อยของT
T
ตัวอย่างเช่น
Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);
แต่ไม่เพียงแค่นั้นกฎการพิมพ์ย่อยของ Java ยังระบุว่าอาเรย์S[]
นั้นเป็นประเภทย่อยของอาเรย์ด้วยT[]
หากS
เป็นประเภทย่อยT
ดังนั้นบางสิ่งเช่นนี้ก็ใช้ได้เช่นกัน:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
เนื่องจากตามกฎการพิมพ์ย่อยใน Java อาร์เรย์Integer[]
จึงเป็นชนิดย่อยของอาร์เรย์Number[]
เนื่องจากจำนวนเต็มเป็นประเภทย่อยของ Number
แต่กฎย่อยนี้สามารถนำไปสู่คำถามที่น่าสนใจ: จะเกิดอะไรขึ้นถ้าเราพยายามทำเช่นนี้?
myNumber[0] = 3.14; //attempt of heap pollution
บรรทัดสุดท้ายนี้จะคอมไพล์ได้ แต่ถ้าเราเรียกใช้รหัสนี้เราจะได้ArrayStoreException
เพราะเรากำลังพยายามใส่ double ลงในอาร์เรย์จำนวนเต็ม ความจริงที่ว่าเรากำลังเข้าถึงอาร์เรย์ผ่านการอ้างอิงตัวเลขนั้นไม่เกี่ยวข้องที่นี่สิ่งที่สำคัญคืออาร์เรย์นั้นเป็นอาร์เรย์ของจำนวนเต็ม
ซึ่งหมายความว่าเราสามารถหลอกผู้แปล แต่เราไม่สามารถหลอกระบบประเภทเวลาทำงาน และนี่ก็เป็นเช่นนั้นเพราะอาร์เรย์เป็นสิ่งที่เราเรียกว่าชนิดที่เรียกคืนได้ ซึ่งหมายความว่าในช่วงเวลาทำงาน Java Number[]
รู้ว่าอาร์เรย์นี้ที่อินสแตนซ์จริงเป็นอาร์เรย์ของจำนวนเต็มซึ่งก็เกิดขึ้นจะเข้าถึงได้ผ่านการอ้างอิงของพิมพ์
อย่างที่เราเห็นสิ่งหนึ่งคือประเภทของวัตถุจริงอีกอย่างคือประเภทของการอ้างอิงที่เราใช้เข้าถึงมันใช่ไหม?
ปัญหาเกี่ยวกับ Java Generics
ตอนนี้ปัญหาเกี่ยวกับประเภททั่วไปใน Java คือข้อมูลประเภทสำหรับพารามิเตอร์ชนิดจะถูกยกเลิกโดยคอมไพเลอร์หลังจากรวบรวมรหัสเสร็จ; ดังนั้นข้อมูลประเภทนี้จะไม่สามารถใช้ได้ในเวลาทำงาน กระบวนการนี้เรียกว่าลบออกประเภท มีเหตุผลที่ดีสำหรับการใช้งาน generics เช่นนี้ใน Java แต่นั่นเป็นเรื่องที่ยาวและเกี่ยวข้องกับความเข้ากันได้ของไบนารีกับโค้ดที่มีอยู่แล้ว
จุดสำคัญที่นี่คือตั้งแต่เวลาทำงานไม่มีข้อมูลประเภทไม่มีวิธีการตรวจสอบให้แน่ใจว่าเราไม่ได้กระทำมลพิษกอง
ลองพิจารณารหัสที่ไม่ปลอดภัยต่อไปนี้
List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution
หากคอมไพเลอร์ Java ไม่ได้หยุดเราไม่ให้ทำสิ่งนี้ระบบชนิดรันไทม์ก็ไม่สามารถหยุดเราได้เช่นกันเนื่องจากไม่มีเวลารันไทม์เพื่อพิจารณาว่ารายการนี้ควรจะเป็นรายการของจำนวนเต็มเท่านั้น Java รันไทม์จะให้เราใส่สิ่งที่เราต้องการลงในรายการนี้เมื่อมันควรจะมีจำนวนเต็มเท่านั้นเพราะเมื่อมันถูกสร้างขึ้นมันถูกประกาศเป็นรายการของจำนวนเต็ม นั่นเป็นสาเหตุที่คอมไพเลอร์ปฏิเสธหมายเลขบรรทัด 4 เพราะไม่ปลอดภัยและหากได้รับอนุญาตอาจทำให้สมมติฐานของระบบประเภทนั้นผิด
ดังนั้นผู้ออกแบบของ Java ทำให้แน่ใจว่าเราไม่สามารถหลอกผู้แปล ถ้าเราไม่สามารถหลอกคอมไพเลอร์ได้ (อย่างที่เราสามารถทำได้กับอาเรย์) จากนั้นเราก็ไม่สามารถหลอกระบบประเภทเวลาทำงานได้เช่นกัน
ด้วยเหตุนี้เราจึงบอกว่าประเภททั่วไปไม่สามารถนำกลับมาใช้ใหม่ได้เนื่องจากในขณะดำเนินการเราไม่สามารถระบุลักษณะที่แท้จริงของประเภททั่วไปได้
ฉันข้ามบางส่วนของคำตอบนี้คุณสามารถอ่านบทความเต็มได้ที่นี่:
https://dzone.com/articles/covariance-and-contravariance