บางคนบอกว่ามันเกี่ยวกับความสัมพันธ์ระหว่างประเภทและประเภทย่อยบางคนบอกว่ามันเกี่ยวกับการแปลงประเภทและคนอื่น ๆ บอกว่ามันถูกใช้เพื่อตัดสินใจว่าเมธอดถูกเขียนทับหรือโอเวอร์โหลด
ทั้งหมดที่กล่าวมา
หัวใจสำคัญคำเหล่านี้อธิบายว่าความสัมพันธ์ประเภทย่อยได้รับผลกระทบจากการแปลงประเภทอย่างไร นั่นคือถ้าA
และB
เป็นประเภทf
คือการแปลงประเภทและ≤ความสัมพันธ์ประเภทย่อย (เช่นA ≤ B
หมายความว่าA
เป็นประเภทย่อยของB
) เรามี
f
เป็นโรคโควาเรียหากA ≤ B
มีนัยอย่างนั้นf(A) ≤ f(B)
f
เป็นสิ่งที่ตรงกันข้ามหากA ≤ B
กล่าวเป็นนัยว่าf(B) ≤ f(A)
f
จะไม่เปลี่ยนแปลงหากไม่มีการระงับทั้งสองข้อข้างต้น
ลองพิจารณาตัวอย่าง Let f(A) = List<A>
ที่List
ถูกประกาศโดย
class List<T> { ... }
คือf
covariant, contravariant หรือคงที่? Covariant จะหมายความว่า a List<String>
เป็นประเภทย่อยของList<Object>
contravariant ที่ a List<Object>
เป็นประเภทย่อยของList<String>
และไม่แปรผันที่ทั้งสองไม่ได้เป็นประเภทย่อยของอีกประเภทหนึ่งกล่าวคือList<String>
และList<Object>
เป็นประเภทที่ไม่สามารถแปลงกลับได้ ใน Java คำหลังเป็นจริงเราพูด (ค่อนข้างไม่เป็นทางการ) ว่ายาสามัญไม่แปรผัน
ตัวอย่างอื่น. ให้f(A) = A[]
. คือf
covariant, contravariant หรือคงที่? นั่นคือ String [] เป็นประเภทย่อยของ Object [], Object [] ประเภทย่อยของ String [] หรือไม่ใช่ประเภทย่อยของอีกประเภทหนึ่ง? (คำตอบ: ใน Java อาร์เรย์เป็นโควาเรียร์)
สิ่งนี้ยังค่อนข้างเป็นนามธรรม เพื่อให้เป็นรูปธรรมมากขึ้นมาดูกันว่าการดำเนินการใดใน Java ถูกกำหนดในแง่ของความสัมพันธ์ประเภทย่อย ตัวอย่างที่ง่ายที่สุดคือการมอบหมายงาน คำสั่ง
x = y;
typeof(y) ≤ typeof(x)
จะรวบรวมเฉพาะในกรณีที่ นั่นคือเราเพิ่งได้เรียนรู้ว่างบ
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
จะไม่คอมไพล์ใน Java แต่
Object[] objects = new String[1];
จะ.
อีกตัวอย่างหนึ่งที่ความสัมพันธ์ประเภทย่อยมีความสำคัญคือนิพจน์การเรียกใช้เมธอด:
result = method(a);
ทางการพูดคำสั่งนี้จะถูกประเมินโดยการกำหนดค่าของพารามิเตอร์แรกของวิธีการดำเนินการแล้วร่างกายของวิธีการและจากนั้นการกำหนดวิธีการที่จะส่งกลับค่าa
result
เช่นเดียวกับที่ได้รับมอบหมายธรรมดาในตัวอย่างที่ผ่านมา "ด้านขวามือ" จะต้องเป็นประเภทย่อยของ "ด้านซ้ายมือ" คือคำสั่งนี้จะไม่สามารถใช้งานได้ถ้าและtypeof(a) ≤ typeof(parameter(method))
returntype(method) ≤ typeof(result)
นั่นคือถ้าวิธีการถูกประกาศโดย:
Number[] method(ArrayList<Number> list) { ... }
ไม่มีนิพจน์ใดต่อไปนี้ที่จะรวบรวม:
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
แต่
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
จะ.
อีกตัวอย่างหนึ่งที่การพิมพ์ย่อยมีผลเหนือกว่า พิจารณา:
Super sup = new Sub();
Number n = sup.method(1);
ที่ไหน
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
ไม่เป็นทางการรันไทม์จะเขียนสิ่งนี้ใหม่เป็น:
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
สำหรับบรรทัดที่ทำเครื่องหมายเพื่อคอมไพล์พารามิเตอร์วิธีการของเมธอดการลบล้างจะต้องเป็นซูเปอร์ไทป์ของพารามิเตอร์วิธีการของเมธอดที่ถูกแทนที่และประเภทส่งคืนเป็นประเภทย่อยของเมธอดที่ถูกแทนที่ การพูดอย่างเป็นทางการf(A) = parametertype(method asdeclaredin(A))
อย่างน้อยต้องมีความแตกต่างและf(A) = returntype(method asdeclaredin(A))
อย่างน้อยก็ต้องเป็นโรคโควาเรีย
สังเกต "อย่างน้อย" ด้านบน สิ่งเหล่านี้เป็นข้อกำหนดขั้นต่ำที่เหมาะสมสำหรับภาษาการเขียนโปรแกรมเชิงวัตถุประเภทคงที่ที่เหมาะสมจะบังคับใช้ แต่ภาษาโปรแกรมอาจเลือกที่จะเข้มงวดมากขึ้น ในกรณีของ Java 1.4 ประเภทพารามิเตอร์และประเภทการส่งคืนเมธอดจะต้องเหมือนกัน (ยกเว้นการลบประเภท) เมื่อแทนที่เมธอดกล่าวคือparametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
เมื่อแทนที่ เนื่องจาก Java 1.5 จึงอนุญาตให้ใช้ชนิดการส่งคืนโควาเรียนได้เมื่อทำการลบล้างกล่าวคือต่อไปนี้จะคอมไพล์ใน Java 1.5 แต่ไม่ใช่ใน Java 1.4:
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
ฉันหวังว่าฉันจะครอบคลุมทุกอย่าง - หรือเป็นรอยขีดข่วนบนพื้นผิว ฉันยังหวังว่ามันจะช่วยให้เข้าใจนามธรรม แต่แนวคิดที่สำคัญของความแปรปรวนประเภท