Cześć! W jednym z poprzednich wykładów omawialiśmy rzutowanie typów pierwotnych. Przypomnijmy krótko, o czym rozmawialiśmy. Typy pierwotne (w tym przypadku numeryczne) reprezentowaliśmy jako lalki zagnieżdżające się w zależności od ilości zajmowanej przez nie pamięci. Jak pamiętacie, umieszczenie mniejszej lalki gniazdowej w większej będzie proste zarówno w prawdziwym życiu, jak i w programowaniu w Javie.
public class Main {
public static void main(String[] args) {
short smallNumber = 100;
int bigNumber = smallNumber;
System.out.println(bigNumber);
}
}
To jest przykład automatycznej konwersji lub rozszerzenia . Dzieje się to samo, więc nie ma potrzeby pisania dodatkowego kodu. Ostatecznie nie robimy nic niezwykłego: po prostu wkładamy mniejszą lalkę lęgową do większej lalki lęgowej. Co innego, jeśli spróbujemy zrobić odwrotnie i włożyć dużą lalkę matrioszkę do mniejszej. Nie można tego zrobić w życiu, ale można to zrobić w programowaniu. Ale jest jedno zastrzeżenie. Jeśli spróbujemy umieścić wartość int
w zmiennej short
, nie będzie to takie proste. W końcu w zmiennej może zmieścić się tylko 16 bitów informacji short
, ale wartość int
zajmuje 32 bity! W rezultacie przesyłana wartość będzie zniekształcona. Kompilator zwróci nam błąd („ stary, robisz coś podejrzanego! ”), ale jeśli wyraźnie określimy, na jaki typ rzutujemy naszą wartość, i tak wykona taką operację.
public class Main {
public static void main(String[] args) {
int bigNumber = 10000000;
bigNumber = (short) bigNumber;
System.out.println(bigNumber);
}
}
W powyższym przykładzie właśnie to zrobiliśmy. Operację zakończono, lecz ponieważ short
w zmiennej zmieściło się tylko 16 z 32 bitów, ostateczna wartość została zniekształcona i w rezultacie otrzymaliśmy liczbę -27008 . Ta operacja nazywa się jawną konwersją lub zawężaniem .
Przykłady wydłużania i skracania typów referencyjnych
Teraz porozmawiamy o tych samych operacjach, ale nie można ich zastosować do typów pierwotnych, ale do obiektów i zmiennych referencyjnych ! Jak to działa w Javie? Właściwie całkiem proste. Istnieją obiekty, które nie są ze sobą powiązane. Logiczne byłoby założenie, że nie można ich konwertować na siebie ani jawnie, ani automatycznie:public class Cat {
}
public class Dog {
}
public class Main {
public static void main(String[] args) {
Cat cat = new Dog();//błąd!
}
}
Tutaj oczywiście otrzymamy błąd. Klasy nie Cat
są Dog
ze sobą powiązane i nie napisaliśmy „konwertera” między sobą. Logiczne jest, że nie będziemy w stanie tego zrobić: kompilator nie ma pojęcia, jak konwertować te obiekty między sobą. Co innego, jeśli obiekty są ze sobą połączone! Jak? Przede wszystkim wykorzystanie dziedziczenia. Spróbujmy stworzyć system małych klas z dziedziczeniem. Będziemy mieć ogólną klasę reprezentującą zwierzęta:
public class Animal {
public void introduce() {
System.out.println("i'm Animal");
}
}
Zwierzęta, jak wiadomo, są domowe i 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 psy - psa domowego i kojota:
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");
}
}
Nasze zajęcia są celowo najbardziej prymitywne, aby ułatwić ich postrzeganie. Pola tutaj tak naprawdę nie są nam potrzebne, wystarczy jedna metoda. Spróbujmy uruchomić następujący kod:
public class Main {
public static void main(String[] args) {
Animal animal = new Pet();
animal.introduce();
}
}
Jak myślisz, co zostanie wyprowadzone na konsolę? introduce
Czy klasa Pet
lub metoda klasowa będzie działać Animal
? Zanim będziesz kontynuować czytanie, spróbuj uzasadnić swoją odpowiedź. A oto wynik! i'm Pet Dlaczego odpowiedź okazała się taka? To proste. Mamy zmienną nadrzędną i obiekt podrzędny. Przez pisanie:
Animal animal = new Pet();
Rozszerzyliśmy typ referencyjnyPet
i zapisaliśmy jego obiekt w zmiennej Animal
. Podobnie jak w przypadku typów pierwotnych, rozszerzanie typów referencyjnych w Javie odbywa się automatycznie. Nie ma potrzeby pisania w tym celu dodatkowego kodu. Teraz mamy obiekt potomny dołączony do odwołania nadrzędnego, w wyniku czego widzimy, że metoda jest wywoływana w klasie potomnej. Jeśli nadal nie do końca rozumiesz, dlaczego ten kod działa, przepisz go prostym językiem:
Животное животное = new ДомашнееЖивотное();
Nie ma z tym problemu, prawda? Wyobraź sobie, że to prawdziwe życie, a łączem w tym przypadku jest prosta papierowa przywieszka z napisem „Zwierzę”. Jeśli weźmiesz taką kartkę papieru i przymocujesz ją do obroży dowolnego zwierzaka, wszystko będzie dobrze. Każde zwierzę to nadal zwierzę! Proces odwrotny, czyli przejście w dół drzewa spadkowego do spadkobierców, jest zawężeniem:
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, do której klasy chcemy rzucić nasz obiekt. Poprzednio mieliśmy zmienną WildAnimal
, a teraz Coyote
, która idzie w dół drzewa dziedziczenia. Logiczne jest, że kompilator nie pominie takiej operacji bez wyraźnego wskazania, ale jeśli określisz typ w nawiasie, wszystko będzie działać. Spójrzmy na inny przykład, bardziej interesujący:
public class Main {
public static void main(String[] args) {
Pet pet = new Animal();//błąd!
}
}
Kompilator zgłasza błąd! Jaki jest powód? Faktem jest, że próbujesz przypisać obiekt nadrzędny do zmiennej podrzędnej. Innymi słowy, chcesz to zrobić:
ДомашнееЖивотное домашнееЖивотное = new Животное();
Ale może jeśli wyraźnie wskażemy typ, na który próbujemy rzutować, to nam się uda? Liczby wydają się działać, 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 Pet. Błąd! Kompilator tym razem nie narzekał, ale w rezultacie otrzymaliśmy wyjątek. Znamy już powód: próbujemy przypisać obiekt nadrzędny do zmiennej podrzędnej. Dlaczego właściwie nie da się tego zrobić? Ponieważ nie wszystkie zwierzęta są zwierzętami domowymi. Utworzyłeś obiekt Animal
i próbujesz przypisać go do zmiennej Pet
. Ale na przykład kojot jest także Animal
, choć nie jest Pet
, zwierzęciem domowym. Innymi słowy, kiedy piszesz:
Pet pet = (Pet) new Animal();
Może tam new Animal()
przebywać każde zwierzę i nie musi to być domowe! Naturalnie twoja zmienna Pet pet
nadaje się tylko do przechowywania zwierząt domowych (i ich potomków), a nie dla wszystkich. Dlatego dla takich przypadków stworzono w Javie specjalny wyjątek – ClassCastException
błąd podczas rzutowania klas. Powiedzmy to jeszcze raz, żeby było jaśniej. Zmienna nadrzędna (odniesienie) może wskazywać na obiekt 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 będziemy mieli żadnych problemów. Mamy obiekt Pet
, na który wskazuje łącze Pet
. Następnie nowy link zaczął wskazywać na ten sam obiekt Animal
. Następnie wykonujemy konwersję animal
do Pet
. Swoją drogą, dlaczego to zrobiliśmy? Ostatnim razem mamy wyjątek! Ponieważ tym razem naszym oryginalnym obiektem jest Pet pet
!
Pet pet = new Pet();
W poprzednim przykładzie był to obiekt Animal
:
Pet pet = (Pet) new Animal();
Do zmiennej potomnej nie można przypisać obiektu nadrzędnego. Wręcz przeciwnie, możesz to zrobić.
GO TO FULL VERSION