Niektórzy twierdzą, że dotyczy relacji między typami i podtypami, inni mówią, że dotyczy konwersji typów, a inni twierdzą, że jest używana do decydowania, czy metoda jest nadpisana, czy przeciążona.
Wszystkie powyższe.
W istocie te terminy opisują, jak na relację podtypów wpływają transformacje typów. Oznacza to, że jeśli A
i B
są typami, f
jest transformacją typu i ≤ relacją podtypu (tj. A ≤ B
Oznacza, że A
jest podtypem B
), mamy
f
jest kowariantny, jeśli to A ≤ B
sugerujef(A) ≤ f(B)
f
jest sprzeczne, jeśli to A ≤ B
sugerujef(B) ≤ f(A)
f
jest niezmienna, jeśli żadna z powyższych nie zachodzi
Rozważmy przykład. Niech f(A) = List<A>
gdzie List
jest zadeklarowane przez
class List<T> { ... }
Czy jest f
kowariantny, kontrawariantny czy niezmienny? Kowariantna oznaczałoby, że List<String>
jest podtypem List<Object>
, kontrawariantny że List<Object>
jest podtypem List<String>
i niezmienna, że nie jest podtypem druga, czyli List<String>
i List<Object>
są niewymienialne typy. W Javie to drugie jest prawdą, mówimy (nieco nieformalnie), że typy generyczne są niezmienne.
Inny przykład. Niech f(A) = A[]
. Czy jest f
kowariantny, kontrawariantny czy niezmienny? To znaczy, czy String [] jest podtypem Object [], Object [] jest podtypem String [], czy też nie jest podtypem drugiego? (Odpowiedź: w Javie tablice są kowariantne)
To wciąż było raczej abstrakcyjne. Aby było to bardziej konkretne, przyjrzyjmy się, które operacje w Javie są zdefiniowane pod względem relacji podtypu. Najprostszym przykładem jest przydział. Twierdzenie
x = y;
skompiluje się tylko wtedy, gdy typeof(y) ≤ typeof(x)
. Oznacza to, że właśnie dowiedzieliśmy się, że oświadczenia
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
nie skompiluje się w Javie, ale
Object[] objects = new String[1];
będzie.
Innym przykładem, w którym relacja podtypu ma znaczenie, jest wyrażenie wywołania metody:
result = method(a);
Mówiąc nieformalnie, ta instrukcja jest oceniana przez przypisanie wartości a
do pierwszego parametru metody, następnie wykonanie treści metody, a następnie przypisanie wartości zwracanej metodom result
. Podobnie jak zwykłe przypisanie w ostatnim przykładzie, „prawa strona” musi być podtypem „lewej strony”, tj. To stwierdzenie może być ważne tylko wtedy, gdy typeof(a) ≤ typeof(parameter(method))
i returntype(method) ≤ typeof(result)
. To znaczy, jeśli metoda jest zadeklarowana przez:
Number[] method(ArrayList<Number> list) { ... }
żadne z poniższych wyrażeń nie zostanie skompilowane:
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
ale
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
będzie.
Kolejny przykład, w którym znaczenie ma podtytuł jest nadrzędne. Rozważać:
Super sup = new Sub();
Number n = sup.method(1);
gdzie
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
Nieformalnie środowisko wykonawcze przepisze to do:
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
Aby zaznaczony wiersz został skompilowany, parametr metody przesłaniającej metody musi być nadtypem parametru metody zastępowanej metody, a typ zwracany jest podtypem metody przesłoniętej. Formalnie rzecz biorąc, f(A) = parametertype(method asdeclaredin(A))
musi być przynajmniej kontrawariantna, a jeśli to f(A) = returntype(method asdeclaredin(A))
przynajmniej kowariantna.
Zwróć uwagę na „co najmniej” powyżej. Są to minimalne wymagania, które będzie wymuszał każdy rozsądny, statycznie bezpieczny, obiektowy język programowania, ale język programowania może być bardziej rygorystyczny. W przypadku Javy 1.4 typy parametrów i zwracane metody muszą być identyczne (z wyjątkiem wymazywania typu) podczas przesłonięcia metod, tj. parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
Podczas przesłaniania. Od wersji Java 1.5 kowariantne typy zwracane są dozwolone podczas zastępowania, tj. Następujące elementy będą kompilowane w Javie 1.5, ale nie w Javie 1.4:
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
Mam nadzieję, że wszystko pokryłem - a raczej zarysowałem powierzchnię. Wciąż mam nadzieję, że pomoże to zrozumieć abstrakcyjną, ale ważną koncepcję wariancji typu.