Cześć! Na poprzedniej lekcji omawialiśmy rzutowanie typów pierwotnych. Przypomnijmy pokrótce, o czym była mowa. Wyobrażaliśmy sobie typy prymitywne (w tym przypadku typy liczbowe) jako zagnieżdżone lalki, które różnią się rozmiarem w zależności od ilości zajmowanej pamięci. Jak zapewne pamiętasz, umieszczenie mniejszej lalki w większej jest proste zarówno w prawdziwym życiu, jak iw programowaniu w Javie.
public class Main {
public static void main(String[] args) {
int bigNumber = 10000000;
short smallNumber = (short) bigNumber;
System.out.println(smallNumber);
}
}
To jest przykład automatycznej konwersji lub poszerzenia . Dzieje się to samo, więc nie musisz pisać dodatkowego kodu. W końcu nie robimy nic niezwykłego: po prostu wkładamy mniejszą lalkę do większej lalki. To inna sprawa, jeśli spróbujemy zrobić coś przeciwnego i włożyć większą rosyjską lalkę do mniejszej lalki. Nie możesz tego zrobić w prawdziwym życiu, ale w programowaniu możesz. Ale jest jeden niuans. Jeśli spróbujemy wstawić zmienną int
do short
zmiennej, sprawy nie potoczą się dla nas tak gładko. W końcu short
zmienna przechowuje tylko 16 bitów informacji, ale int
zajmuje 32 bity! W rezultacie przekazywana wartość jest zniekształcona. Kompilator zwróci nam błąd („ Stary, robisz coś podejrzanego!'). Ale jeśli wyraźnie wskażemy typ, na który konwertujemy naszą wartość, przejdzie dalej i wykona operację.
public class Main {
public static void main(String[] args) {
int bigNumber = 10000000;
bigNumber = (short) bigNumber;
System.out.println(bigNumber);
}
}
Właśnie to zrobiliśmy w powyższym przykładzie. Operacja została wykonana, ale ponieważ short
zmienna może pomieścić tylko 16 z 32 bajtów, ostateczna wartość jest zniekształcona i otrzymujemy liczbę -27008 . Taka operacja nazywana jest jawną konwersją lub zawężeniem .
Przykłady poszerzania i zawężania typów referencyjnych
Porozmawiajmy teraz o tych samych operatorach stosowanych nie do typów pierwotnych, ale do obiektów i zmiennych referencyjnych ! Jak to działa w Javie? To właściwie całkiem proste. Istnieją przedmioty, które nie są ze sobą powiązane. Logiczne byłoby założenie, że nie można ich wzajemnie przekonwertować, ani jawnie, ani automatycznie:
public class Cat {
}
public class Dog {
}
public class Main {
public static void main(String[] args) {
Cat cat = new Dog(); // Error!
}
}
Tutaj oczywiście pojawia się błąd. Klasy Cat
i Dog
nie są ze sobą powiązane i nie napisaliśmy „konwertera”, aby przechodzić z jednej do drugiej. Ma sens, że nie możemy tego zrobić: kompilator nie ma pojęcia, jak przekonwertować te obiekty z jednego typu na drugi. Jeśli obiekty są ze sobą powiązane, cóż, to inna sprawa! Powiązane jak? Przede wszystkim poprzez dziedziczenie. Spróbujmy wykorzystać dziedziczenie do stworzenia małego systemu klas. Będziemy mieli wspólną klasę reprezentującą zwierzęta:
public class Animal {
public void introduce() {
System.out.println("I'm Animal");
}
}
Wszyscy wiedzą, że zwierzęta mogą być udomowione (zwierzęta domowe) lub dzikie:
public class WildAnimal extends Animal {
public void introduce() {
System.out.println("I'm WildAnimal");
}
}
public class Pet extends Animal {
public void introduce() {
System.out.println("I'm Pet");
}
}
Weźmy na przykład kły — mamy psy domowe i kojoty:
public class Dog extends Pet {
public void introduce() {
System.out.println("I'm Dog");
}
}
public class Coyote extends WildAnimal {
public void introduce() {
System.out.println ("I'm Coyote");
}
}
Specjalnie wybraliśmy najbardziej podstawowe klasy, aby były łatwiejsze do zrozumienia. Tak naprawdę nie potrzebujemy żadnych pól, wystarczy jedna metoda. Spróbujmy wykonać ten kod:
public class Main {
public static void main(String[] args) {
Animal animal = new Pet();
animal.introduce();
}
}
Jak myślicie, co będzie wyświetlane na konsoli? Czy zostanie wywołana introduce
metoda klasy Pet
lub klasa? Animal
Spróbuj uzasadnić swoją odpowiedź przed kontynuowaniem czytania. A oto wynik! Jestem zwierzakiem Dlaczego to dostaliśmy? To wszystko jest proste. Mamy zmienną nadrzędną i obiekt podrzędny. Przez pisanie,
Animal animal = new Pet();
rozszerzyliśmy referencjęPet
i przypisaliśmy ją do Animal
zmiennej . Podobnie jak w przypadku typów pierwotnych, typy referencyjne są automatycznie rozszerzane w Javie. Nie musisz pisać dodatkowego kodu, aby to się stało. Teraz mamy obiekt potomny przypisany do odniesienia nadrzędnego. W rezultacie widzimy, że wywołanie metody jest wykonywane na klasie potomnej. Jeśli nadal nie do końca rozumiesz, dlaczego ten kod działa, przepisz go prostym językiem:
Animal animal = new DomesticatedAnimal();
Nie ma z tym problemu, prawda? Wyobraź sobie, że to jest prawdziwe życie, a referencją jest po prostu papierowa etykieta z napisem „Zwierzę”. Jeśli weźmiesz tę kartkę papieru i przyczepisz ją do obroży dowolnego zwierzaka, wszystko będzie dobrze. W końcu każde zwierzę to zwierzę! Proces odwrotny — przechodzenie w dół drzewa dziedziczenia do potomków — zawęża się:
public class Main {
public static void main(String[] args) {
WildAnimal wildAnimal = new Coyote();
Coyote coyote = (Coyote) wildAnimal;
coyote.introduce();
}
}
Jak widać, tutaj wyraźnie wskazujemy klasę, na którą chcemy przekonwertować nasz obiekt. Wcześniej mieliśmy WildAnimal
zmienną, a teraz mamy Coyote
, która jest niżej w drzewie dziedziczenia. Ma sens, że bez wyraźnego wskazania kompilator nie zezwoli na taką operację, ale jeśli typ podamy w nawiasach, to wszystko działa. Rozważ inny, bardziej interesujący przykład:
public class Main {
public static void main(String[] args) {
Pet pet = new Animal(); // Error!
}
}
Kompilator generuje błąd! Ale dlaczego? Ponieważ próbujesz przypisać obiekt nadrzędny do odniesienia podrzędnego. Innymi słowy, próbujesz zrobić coś takiego:
DomesticatedAnimal domesticatedAnimal = new Animal();
Cóż, może wszystko zadziała, jeśli jednoznacznie określimy typ, na który próbujemy dokonać konwersji? To zadziałało z liczbami — spróbujmy! :)
public class Main {
public static void main(String[] args) {
Pet pet = (Pet) new Animal();
}
}
Wyjątek w wątku „main” java.lang.ClassCastException: Nie można rzucić zwierzęcia na zwierzaka Błąd! Kompilator tym razem nie nakrzyczał na nas, ale skończyło się na wyjątku. Znamy już przyczynę: próbujemy przypisać obiekt nadrzędny do referencji podrzędnej. Ale właściwie dlaczego nie możesz tego zrobić? Ponieważ nie wszystkie zwierzęta są zwierzętami udomowionymi. Stworzyłeś Animal
obiekt i próbujesz przypisać go do Pet
zmiennej. Kojot jest również Animal
, ale nie jest Pet
. Innymi słowy, kiedy piszesz
Pet pet = (Pet) new Animal();
new Animal()
może reprezentować dowolne zwierzę, niekoniecznie zwierzę domowe! Oczywiście twoja Pet pet
zmienna nadaje się tylko do przechowywania zwierząt domowych (i ich potomków), a nie dowolnego rodzaju zwierząt. Dlatego ClassCastException
utworzono specjalny wyjątek Java, , dla przypadków, w których wystąpił błąd podczas rzutowania klas. Przeanalizujmy to jeszcze raz, aby wszystko było jaśniejsze. Odniesienie do rodzica może wskazywać na instancje klasy potomnej:
public class Main {
public static void main(String[] args) {
Pet pet = new Pet();
Animal animal = pet;
Pet pet2 = (Pet) animal;
pet2.introduce();
}
}
Na przykład tutaj nie mamy problemów. Mamy Pet
obiekt, do którego odwołuje się Pet
zmienna. Później Animal
odniesienie wskazywało na ten sam obiekt. Następnie konwertujemy animal
do Pet
. Swoją drogą, dlaczego nam się to udało? Ostatnim razem mamy wyjątek! Ponieważ tym razem naszym oryginalnym obiektem jest Pet
!
Pet pet = new Pet();
Ale w ostatnim przykładzie był to Animal
obiekt:
Pet pet = (Pet) new Animal();
Nie można przypisać obiektu przodka do zmiennej potomnej. Możesz zrobić odwrotnie.
Więcej czytania: |
---|
GO TO FULL VERSION