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 Ai Bsą typami, fjest transformacją typu i ≤ relacją podtypu (tj. A ≤ BOznacza, że Ajest podtypem B), mamy
fjest kowariantny, jeśli to A ≤ Bsugerujef(A) ≤ f(B)
fjest sprzeczne, jeśli to A ≤ Bsugerujef(B) ≤ f(A)
f jest niezmienna, jeśli żadna z powyższych nie zachodzi
Rozważmy przykład. Niech f(A) = List<A>gdzie Listjest zadeklarowane przez
class List<T> { ... }
Czy jest fkowariantny, 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 fkowariantny, 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 ado 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.