CodeGym /Kursy Java /Moduł 2: Rdzeń Java /Rzutowanie typów. Konwersje rozszerzające i zawężające

Rzutowanie typów. Konwersje rozszerzające i zawężające

Moduł 2: Rdzeń Java
Poziom 7 , Lekcja 1
Dostępny

– Cześć, Amigo! Tematem dzisiejszej lekcji są rozszerzające i zawężające konwersje typów. O rozszerzaniu i zawężaniu typów prymitywnych dowiedziałeś się już eony temu. Na Poziomie 10. Dzisiaj porozmawiamy o tym, jak to działa w przypadku typów referencyjnych, np. instancji klas.

Tak naprawdę, to wszystko jest całkiem proste. Wyobraźmy sobie łańcuch dziedziczenia klasy: klasa, jej klasa macierzysta, klasa macierzysta klasy macierzystej itd., aż do klasy Object. Ponieważ każda klasa zawiera wszystkie metody należące do klasy, którą sama dziedziczy, to instancja owej klasy może zostać zapisana w zmiennej o typie jej dowolnej klasy macierzystej.

Oto przykład:

Kod Opis
class Animal
{
public void doAnimalActions();
}class Cat extends Animal
{
public void doCatActions();
}class Tiger extends Cat
{
public void doTigerActions();
}
Mamy tutaj deklaracje trzech klas: Animal, Cat oraz Tiger. Cat dziedziczy Animal. Za to Tiger dziedziczy Cat.
public static void main(String[] args)
{
Tiger tiger = new Tiger();
Cat cat = new Tiger();
Animal animal = new Tiger();
Object obj = new Tiger();
}
Obiekt Tiger może zawsze zostać przypisany do zmiennej, której typ jest typem jednego z jej przodków. W przypadku klasy Tiger są to Cat, Animal i Object.

Przyjrzyjmy się teraz konwersjom rozszerzającym i zawężającym.

Jeśli operacja przypisania powoduje, że przesuwamy się w górę łańcucha dziedziczenia (w kierunku klasy Object), to mamy do czynienia z konwersją rozszerzającą (znaną również jako rzutowanie w górę, ang. upcasting). Jeśli przesuniemy się w dół łańcucha w kierunku typu obiektu, to jest to konwersja zawężająca (znana również jako rzutowanie w dół, ang. downcasting).

Poruszanie się w górę łańcucha dziedziczenia nazywane jest rozszerzaniem, ponieważ prowadzi do bardziej ogólnego typu. W ten sposób tracimy jednak możliwość wywoływania metod dodanych do klasy poprzez dziedziczenie.

Kod Opis
public static void main(String[] args)
{
Object obj = new Tiger();
Animal animal = (Animal) obj;
Cat cat = (Cat) obj;
Tiger tiger = (Tiger) animal;
Tiger tiger2 = (Tiger) cat;
}
Przy zawężaniu typu musisz użyć operatora konwersji typu, tzn. wykonać jawną konwersję.

Sprawia to, że maszyna Javy sprawdza, czy dany obiekt rzeczywiście dziedziczy właśnie ten typ, na który chcemy go przekonwertować.

Ot ta maciupeńka innowacja pozwoliła na wielokrotne zmniejszenie liczby błędów związanych z rzutowaniem, jednocześnie znacznie zwiększając stabilność oprogramowania pisanego w Javie.

Kod Opis
public static void main(String[] args)
{
Object obj = new Tiger();
if (obj instanceof Cat)
{
Cat cat = (Cat) obj;
cat.doCatActions();
}}
Użyj lepiej sprawdzenia instanceof
public static void main(String[] args)
{
Animal animal = new Tiger();
doAllAction(animal);

Animal animal2 = new Cat();
doAllAction(animal2);

Animal animal3 = new Pet();
doAllAction(animal3);
}

public static void doAllAction(Animal animal)
{
if (animal instanceof Tiger)
{
Tiger tiger = (Tiger) animal;
tiger.doTigerActions();
}

if (animal instanceof Cat)
{
Cat cat = (Cat) animal;
cat.doCatActions();
}

animal.doAnimalActions();
}
A oto, dlaczego tak jest. Spójrz na przykład po lewej.

Nie zawsze wiemy (czy też wie o tym nasz kod), z jakim typem obiektu przyszło nam pracować. Może to być obiekt tego samego typu, co zmienna (Animal) lub dowolny typ potomny (Cat, Tiger).

Przyjrzyj się metodzie doAllAction. Metoda działa poprawnie niezależnie od typu przekazanego do niej obiektu.

Innymi słowy, działa ona poprawnie dla wszystkich trzech typów: Animal, Cat oraz Tiger.

public static void main(String[] args)
{
Cat cat = new Tiger();
Animal animal = cat;
Object obj = cat;
}
Mamy tu do czynienia z trzema operacjami przypisania. Wszystkie z nich są przykładami konwersji rozszerzających.

Operator rzutowania typu na nic się nie przyda, dlatego że żadne sprawdzanie nie jest tu konieczne. Referencja do obiektu może być zawsze zapisana w zmiennej, której typ odpowiada typowi dowolnego z jej przodków.

Och, te przykłady od drugiego do ostatniego wszystko mi wyjaśniły: dlaczego potrzebna jest kontrola i dlaczego konieczne jest rzutowanie typu.

– Mam nadzieję. Zwróć jeszcze uwagę, że...

Żaden z tych czynników nie powoduje jakichkolwiek zmian w obiekcie! Jedyne, co się zmienia, to liczba metod możliwych do wywołania na danej zmiennej referencyjnej.

Na przykład, zmienna Cat pozwala na wywołanie metod doAnimalActions i doCatActions. Zmienna ta nic nie wie o metodzie doTigerActions, nawet jeśli wskazuje ona na obiekt Tiger.

Tak, rozumiem. To było dużo prostsze, niż się spodziewałem.

Komentarze (2)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Mateusz Poziom 15, Poland, Poland
14 sierpnia 2022
In short, are we restricting to remove some methods in the new object? And that in future, the programmer will not let fly a bird whose wing has been cut off. Am I right?
Piotr Kat Poziom 41, Netherlands
14 lipca 2023
It depends on which direction you go in, upcasting or downcasting. One will limit the methods, while the other will expand them. If you write correctly what you want, you will have access exactly to those methods that you should have access to in that particular place.