Một số người nói rằng nó là về mối quan hệ giữa các kiểu và kiểu con, những người khác nói rằng nó là về chuyển đổi kiểu và những người khác nói rằng nó được sử dụng để quyết định xem một phương thức có bị ghi đè hay quá tải hay không.
Tất cả những điều trên.
Về cơ bản, các thuật ngữ này mô tả mối quan hệ kiểu con bị ảnh hưởng như thế nào bởi các phép biến đổi kiểu. Nghĩa là, nếu A
và B
là các kiểu, f
là một phép biến đổi kiểu và ≤ quan hệ kiểu con (nghĩa A ≤ B
là đó A
là một kiểu con của B
), chúng ta có
f
là hiệp phương sai nếu A ≤ B
ngụ ý rằngf(A) ≤ f(B)
f
là trái ngược nếu A ≤ B
ngụ ý rằngf(B) ≤ f(A)
f
là bất biến nếu cả hai điều trên đều không
Hãy xem xét một ví dụ. Hãy để f(A) = List<A>
nơi List
được khai báo bởi
class List<T> { ... }
Đồng f
biến, đối nghịch hay bất biến? Đồng biến có nghĩa là a List<String>
là một kiểu con của List<Object>
, đối nghịch rằng a List<Object>
là một kiểu con của List<String>
và bất biến mà không phải là một kiểu con của kiểu kia, tức là List<String>
và List<Object>
là những kiểu không thể thay đổi được. Trong Java, điều sau là đúng, chúng tôi nói (hơi không chính thức) rằng generic là bất biến.
Một vi dụ khac. Hãy để f(A) = A[]
. Đồng f
biến, đối nghịch hay bất biến? Nghĩa là, Chuỗi [] là kiểu con của Đối tượng [], Đối tượng [] là kiểu con của Chuỗi [], hay không phải là kiểu con của đối tượng kia? (Trả lời: Trong Java, các mảng là hiệp phương sai)
Điều này vẫn còn khá trừu tượng. Để làm cho nó cụ thể hơn, hãy xem các phép toán nào trong Java được định nghĩa theo quan hệ kiểu con. Ví dụ đơn giản nhất là phân công. Tuyên bố
x = y;
sẽ biên dịch chỉ nếu typeof(y) ≤ typeof(x)
. Đó là, chúng ta vừa biết rằng các câu lệnh
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
sẽ không biên dịch bằng Java, nhưng
Object[] objects = new String[1];
sẽ.
Một ví dụ khác trong đó quan hệ kiểu con quan trọng là một biểu thức gọi phương thức:
result = method(a);
Nói một cách không chính thức, câu lệnh này được đánh giá bằng cách gán giá trị của a
cho tham số đầu tiên của phương thức, sau đó thực thi phần thân của phương thức, rồi gán giá trị trả về cho phương thức result
. Giống như phép gán đơn giản trong ví dụ trước, "phía bên phải" phải là một kiểu con của "phía bên trái", tức là câu lệnh này chỉ có thể hợp lệ nếu typeof(a) ≤ typeof(parameter(method))
và returntype(method) ≤ typeof(result)
. Đó là, phương thức if được khai báo bởi:
Number[] method(ArrayList<Number> list) { ... }
không có biểu thức nào sau đây sẽ biên dịch:
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
nhưng
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
sẽ.
Một ví dụ khác trong đó vấn đề định kiểu con được ghi đè. Xem xét:
Super sup = new Sub();
Number n = sup.method(1);
Ở đâu
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
Không chính thức, thời gian chạy sẽ viết lại điều này thành:
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
Đối với dòng được đánh dấu để biên dịch, tham số phương thức của phương thức ghi đè phải là siêu kiểu của tham số phương thức của phương thức bị ghi đè và kiểu trả về là kiểu con của phương thức bị ghi đè. Về mặt hình thức, f(A) = parametertype(method asdeclaredin(A))
ít nhất phải là tương phản, và nếu f(A) = returntype(method asdeclaredin(A))
ít nhất phải là hiệp phương sai.
Lưu ý "ít nhất" ở trên. Đó là những yêu cầu tối thiểu mà bất kỳ ngôn ngữ lập trình hướng đối tượng an toàn kiểu tĩnh hợp lý nào sẽ thực thi, nhưng một ngôn ngữ lập trình có thể chọn khắt khe hơn. Trong trường hợp của Java 1.4, kiểu tham số và kiểu trả về phương thức phải giống hệt nhau (ngoại trừ kiểu xóa) khi ghi đè phương thức, tức là parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
khi ghi đè. Kể từ Java 1.5, các kiểu trả về hiệp biến được phép khi ghi đè, tức là những kiểu sau sẽ được biên dịch trong Java 1.5, nhưng không phải trong Java 1.4:
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
Tôi hy vọng tôi đã che đậy mọi thứ - hay đúng hơn là làm trầy xước bề mặt. Tôi vẫn hy vọng nó sẽ giúp hiểu được khái niệm trừu tượng, nhưng quan trọng về phương sai kiểu.