Jason. Cała informatyka w jednym miejscu!

O typach wieloznacznych w języku Java kilkaset słów więcej...skoro było o kowariancji, teraz pokażę jak kontrawariancja wpływa na to, co możesz zrobić z elementami danej kolekcji. Wymagana wiedza na temat typu wieloznacznego ("wildcard")!

KONTRAWARIANCJA W JĘZYKU JAVA DRUGĄ STRONĄ OGRANICZEŃ

Kontrawariancja (ang. contravariance) odnosi się do ograniczenia nadtypu dla typów wieloznacznych (ang. wildcard) i to w przeciwieństwie do kowariancji, jest operacją której nie wykonasz na typie generycznym T. Polega to na tym, że dysponując dowolną strukturą wykorzystującą typ generyczny np. kolekcją (taką, jak "ArrayList"), możemy nałożyć ograniczenie w drugą stronę, czyli od podanej klasy bazowej do JEJ klas bazowych (aż do "Object"). Oczywiście ma to swoje konsekwencje w kwestii naszych możliwości, jednak wszystko się zacznie wyjaśniać stopniowo.

HISTORIA PODOBNA, LECZ TROCHĘ BARDZO INNA 😁

Kontrawariancja w języku Java jest tworzona bardzo podobnie do kowariancji, tylko zamieniamy słowo kluczowe "extends" na "super", ponieważ teraz robimy w drugą stronę - ograniczamy przez "nadtypy" podanej klasy, czyli idziemy w stronę klasy "Object", która jest "matką" wszystkich klas. Ponownie zaczynamy od tych dwóch przykładów. Czym się różni to:

private void addToList(List<BaseClass> list)

od tego 🤔?

private void addToList(List< ? super BaseClass> list)

Podobnie jak było przy kowariancji, tutaj mamy znaczną różnicę. Inwariancja (pierwszy przykład) dopuszcza pod swoje skrzydła tylko klasę "BaseClass" i nic więcej ⛔. Drugi zapis (kowariantny) zezwala na przekazywanie kolekcji z typami klas bazowych podanej klasy, jak i samą podaną klasę, oczywiście 🙂. To znaczy, że masz prawo przekazać listę typu "Object", typu "BaseClass" oraz wszystkich innych tych klas, które tworzyłyby "sznurek" dziedziczenia od "Object" do "BaseClass". Także te operacje będą "legitne":

ArrayList<Object> objectsList = new ArrayList<>();
ArrayList<BaseClass> baseClassesList = new ArrayList<>();

addToList(objectsList);
addToList(baseClassesList);

Kompilator na to przystanie ✔️. Po raz kolejny patrzy się na bezpieczeństwo typów i ono nie zostaje naruszone.

OTWARTA NA ZAPIS, ZAMKNIĘTA NA ODCZYT

Nie bez kozery jest taki nagłówek 🙃. Kontrawariancja w języku Java stanowi odwrotność kowariancji - zapis elementów dozwolony, tylko odczyt nie. A to dlatego, że kompilator nie wie czego się spodziewać ze strony metody pobierającej. Dlatego też typem otrzymanej wartości będzie "Object":

Object firstElement = list.getFirst();

Co innego w kwestii zapisywania elementów. Jest możliwość przekazywania typów jako klas potomnych klasy "BaseClass" oraz jej samej np. poprzez metodę "add":

list.add(new BaseClass());
list.add(new DerivedClass());

Oto jaki płynie z tego wniosek. Typy wieloznaczne z ograniczeniem nadtypu pozwalają na zapis w strukturach generycznych, lecz niemożliwy jest z nich odczyt, ponieważ kompilator "widzi" jedynie typ "Object" (działa jak tryb "tylko do zapisu"). Można do metod przekazywać parametry, lecz nie można przedostać się do ich wartości zwrotnych. W drugą stronę działa kowariancja - można odczytywać wartości, lecz nie wolno niczego zapisywać (tryb "tylko do odczytu"). Z tego powodu powstał nawet mnemonik PECS, czyli "Producer Extends, Consumer Super". W przełożeniu na polski oznacza tyle, żeby kontrawariancji używać gdy zażyczymy sobie tylko zapisywania elementów, a kowariancji, gdy zechcemy jedynie odczytywać elementy.

Kontrawariancja w języku Java

Kontrawariancja w języku Java to inaczej ograniczenie nadtypu dla typu wieloznacznego. Przeprowadzanie operacji na elementach jest dostępne tylko w kontekście zapisu.


Tak oto doszliśmy do końca opowieści o kontrawariancji. Jeżeli to dla Ciebie czarna magia 🎱, nie przejmuj się 😅 - to jeden z tych grubszych tematów Javy.

PODOBNE ARTYKUŁY