2011-12-12 8 views
81

Dzisiaj przeczytałem kilka artykułów na temat kowariancji, kontrawariancji (i niezmienności) w Javie. Przeczytałem angielski i niemiecki artykuł wikipedia oraz kilka innych blogów i artykułów od IBM.Kowariancja, niezmienniczość i kontrawariancja wyjaśnione prostym językiem angielskim?

Ale wciąż jestem trochę zdezorientowany, o co dokładnie chodzi? Niektórzy twierdzą, że chodzi o związek między typami i podtypami, niektórzy twierdzą, że chodzi o konwersję typu, a niektórzy twierdzą, że jest używany do decydowania, czy metoda jest nadpisywana czy przeciążana.

Poszukuję więc łatwego wyjaśnienia w prostym języku angielskim, który pokazuje początkującym czym jest kowariancja i kontrawariancja (i niezmienniczość). Plus punkt za łatwy przykład.

+0

Proszę odnieść się do tego postu, może to być pomocne dla Ciebie: http://stackoverflow.com/q/2501023/218717 –

+3

Może lepiej stos typu wymiana pytanie programisty. Jeśli publikujesz tam, zastanów się nad tym, co rozumiesz i co cię szczególnie denerwuje, ponieważ teraz prosisz kogoś o ponowne napisanie całego samouczka. –

Odpowiedz

199

Niektórzy uważają, że jest o relationshop pomiędzy typów i podtypów, inne znaczy chodzi o rodzaj konwersji i inni twierdzą, że jest wykorzystywany do określenia, czy metoda jest zastąpione lub przeciążenia.

Wszystkie powyższe.

W istocie te warunki opisują, w jaki sposób transformacja typu wpływa na relację podtypu. Oznacza to, że jeśli A i B są typy, f jest transformacja typu i ≤ relacji podtypu (tj A ≤ B oznacza, że ​​A jest podtypem B), mamy

  • f jest kowariantna jeśli A ≤ B oznacza, że ​​f(A) ≤ f(B)
  • f jest kontrawariantny jeśli A ≤ B oznacza, że ​​f(B) ≤ f(A)
  • f jest niezmienna, jeśli żadna z powyższych posiada

Rozważmy przykład. Niech f(A) = List<A> gdzie List deklaruje

class List<T> { ... } 

Czy f kowariantna, kontrawariantny lub niezmiennego? Kowariantna oznaczałoby, że List<String> jest podtypem List<Object>, kontrawariantny że List<Object> jest podtypem List<String> i niezmienna, że ​​nie jest podtypem z drugiej strony, to znaczy List<String> i List<Object> są niewymienialne typy. W języku Java, ta ostatnia jest prawdziwa, mówimy (nieco nieformalnie), że generics są niezmienne.

Inny przykład. Niech f (A) = A[]. Czy f jest kowariantny, sprzeczny lub niezmienny? Oznacza to, że String [] jest podtypem Object [], Object [] podtypu String [], czy też nie jest podtypem drugiego? (Odpowiedź: W Javie tablice są kowariantne)

To wciąż było dość abstrakcyjne. Aby było to bardziej konkretne, przyjrzyjmy się, które operacje w Javie są zdefiniowane pod kątem relacji podtypu. Najprostszym przykładem jest przydział. Oświadczenie

x = y; 

będzie skompilować tylko jeśli typeof (y) ≤ typeof (x).Oznacza to, że właśnie dowiedział się, że oświadczenia

ArrayList<String> strings = new ArrayList<Object>(); 
ArrayList<Object> objects = new ArrayList<String>(); 

nie będzie kompilować w Javie, ale

Object[] objects = new String[1]; 

wola.

Innym przykładem, gdzie liczy się relacja podtyp jest wyrażeniem wywołania metody:

result = method(a); 

Nieformalnie mówiąc, to stwierdzenie jest oceniany przez przypisanie wartości a do sposobu pierwszego parametru, a następnie wykonanie ciało metody , a następnie przypisanie metod zwraca wartość do result. Podobnie jak zwykłe przypisanie w ostatnim przykładzie, "prawa strona" musi być podtypem "lewej strony", tj. Ta instrukcja może być ważna tylko wtedy, gdy typeof (a) ≤ typeof (parametr (metoda)) i returntype (metoda) ≤ typeof (wynik). Oznacza to, że jeśli metoda jest zadeklarowana przez:

Number[] method(ArrayList<Number> list) { ... } 

żaden z poniższych wyrażeń skompiluje:

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>()); 

wola.

Kolejny przykład, w którym podrzędność ma znaczenie. Rozważmy:

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 będzie przepisać to:

class Super { 
    Number method(Number n) { 
     if (this instanceof Sub) { 
      return ((Sub) this).method(n); // * 
     } else { 
      ... 
     } 
    } 
} 

Dla zaznaczonej linii skompilować, parametr metoda metody nadrzędnego musi być supertype parametru metody zastąpionej metody, a typ zwracany - podtypu metody nadpisanej. Formalnie f (A) = parametr typu (metoda asdeclaredin (A)) musi być przynajmniej kontrowersyjna, a jeśli f (A) = returntype (metoda asdeclaredin (A)) musi przynajmniej być kowariantna.

Uwaga "przynajmniej" powyżej. Są to minimalne wymagania, które wymusi każdy rozsądny statycznie bezpieczny język programowania obiektowego, ale język programowania może być bardziej restrykcyjny. W przypadku języka Java 1.4 typy parametrów i metody zwracania typów muszą być identyczne (z wyjątkiem wymazywania typu), gdy nadpisywane są metody, tj. Parametr typu (metoda asdeclaredin (A)) = parametr typu (metoda asdeclaredin (B)) podczas przesłonięcia. Ponieważ Java 1.5, kowariantna typy zwracane są dozwolone, gdy nadrzędne, czyli dodaje się skompilować w Java 1.5, ale nie w Java 1.4:

class Collection { 
    Iterator iterator() { ... } 
} 

class List extends Collection { 
    @Override 
    ListIterator iterator() { ... } 
} 

Mam nadzieję, że wszystko pokryte - czy raczej powierzchownie. Nadal mam nadzieję, że pomoże to zrozumieć abstrakcyjną, ale ważną koncepcję wariancji typu.

+0

Ponadto, ponieważ typy argumentów przeciwstawnych Java 1.5 są dozwolone podczas przesłonięcia. Myślę, że tęskniłeś. –

+10

Czy oni są? Po prostu wypróbowałem to w czasie zaćmienia, a kompilator myślał, że mam zamiar przeładować, a nie przesłonić, i odrzucił kod, gdy umieściłem adnotację @Override w metodzie podklasy. Czy masz jakieś dowody na twoje twierdzenie, że Java obsługuje sprzeczne typy argumentów? – meriton

+0

Ach, masz rację. Wierzyłem komuś, nie sprawdzając tego sam. –

7

Biorąc system typu Java i następnie klas:

Każdy obiekt pewnego typu T mogą być podstawione przez przedmiot podtypu T.

TYP VARIANCE - metod klasy następujące skutki

class A { 
    public S f(U u) { ... } 
} 

class B extends A { 
    @Override 
    public T f(V v) { ... } 
} 

B b = new B(); 
t = b.f(v); 
A a = ...; // Might have type B 
s = a.f(u); // and then do V v = u; 

można zauważyć, że:

  • T musi być podtyp S (kowariantna, a B jest podtyp).
  • V musi być nadtypem U (contravariant, jako kierunek dziedziczenia przeciwnego).

Obecnie kojarzą i przeciwstawiają się B będąc podtypem A. Można wprowadzić następujące silniejsze typowania z bardziej szczegółową wiedzą. W podtypie.

Kowariancja (dostępna w Javie) jest przydatna, mówiąc, że zwraca się bardziej konkretny wynik w podtypie; szczególnie widoczne, gdy A = T i B = S. Kontrawariancja mówi, że jesteś przygotowany na bardziej ogólną argumentację.

Powiązane problemy