你好!在之前的一講中,我們討論了原始型別的轉換。讓我們簡單回憶一下我們正在談論的內容。 我們根據原始類型(在本例中為數字)佔用的記憶體量將它們表示為嵌套娃娃。您還記得,無論是在現實生活中還是在 Java 程式設計中,將較小的嵌套娃娃放入較大的嵌套娃娃中都是很簡單的。
public class Main {
public static void main(String[] args) {
short smallNumber = 100;
int bigNumber = smallNumber;
System.out.println(bigNumber);
}
}
這是自動轉換或擴展 的範例。它會自行發生,因此無需編寫額外的程式碼。最後,我們並沒有做任何不尋常的事情:我們只是將一個較小的嵌套娃娃放入較大的嵌套娃娃中。如果我們反其道而行之,將一個大的俄羅斯娃娃放入一個較小的俄羅斯娃娃中,那就是另一回事了。這在人生中是做不到的,但是在程式設計上卻可以做到。但有一點要注意。如果我們嘗試將一個值int
放入變數中short
,則不會那麼容易。畢竟,變數只能容納 16 位元訊息short
,但值卻int
需要 32 位元!結果,傳輸的值將會失真。編譯器會給我們一個錯誤(「夥計,你正在做一些可疑的事情!」),但是如果我們明確指定我們將值轉換為什麼類型,它仍然會執行這樣的操作。
public class Main {
public static void main(String[] args) {
int bigNumber = 10000000;
bigNumber = (short) bigNumber;
System.out.println(bigNumber);
}
}
在上面的例子中,我們就是這樣做的。操作已完成,但由於short
32 位元中只有 16 位元適合該變量,因此最終值被扭曲,結果我們收到了數字-27008。此操作稱為明確轉換或縮小。
引用類型的擴展和收縮範例
現在我們將討論相同的操作,但不適用於基本類型,而是適用於物件和引用變數!這在 Java 中是如何運作的?其實很簡單。有些物件彼此不相關。假設它們不能明確或自動地相互轉換是合乎邏輯的:public class Cat {
}
public class Dog {
}
public class Main {
public static void main(String[] args) {
Cat cat = new Dog();//error!
}
}
當然,我們會得到一個錯誤。這些類別彼此不相關,我們也沒有寫一個從一個類別到另一個類別的「轉換器」Cat
。Dog
從邏輯上講,我們無法做到這一點:編譯器不知道如何在這些物件之間進行轉換。如果物體互相連接那就是另一回事了!如何?首先,使用繼承。讓我們嘗試建立一個帶有繼承的小型類別系統。我們將有一個代表動物的通用類別:
public class Animal {
public void introduce() {
System.out.println("i'm Animal");
}
}
如您所知,動物有家養動物和野生動物:
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");
}
}
以狗為例──一隻家狗和一隻土狼:
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");
}
}
我們的類別故意是最原始的,以使它們更容易被感知。我們這裡並不真正需要字段,一種方法就足夠了。讓我們嘗試運行以下程式碼:
public class Main {
public static void main(String[] args) {
Animal animal = new Pet();
animal.introduce();
}
}
你認為控制台會輸出什麼?introduce
類別Pet
或類別方法會起作用嗎Animal
?在繼續閱讀之前嘗試證明你的答案是正確的。這就是結果! 我是 Pet 為什麼答案是這樣的?這很簡單。我們有一個父變數和一個子物件。透過寫:
Animal animal = new Pet();
我們擴展了引用類型Pet
並將其物件儲存在變數中Animal
。與原始類型一樣,Java 中引用類型的擴充功能是自動完成的。無需為此編寫額外的程式碼。現在我們有一個子物件附加到父引用,結果我們看到該方法在子類別上被呼叫。如果您仍然不完全理解這段程式碼的工作原理,請用簡單的語言重寫它:
Животное животное = new ДомашнееЖивотное();
這沒有問題,對吧?想像一下這是現實生活,本例中的連結是一個簡單的紙質標籤,上面寫著「動物」。如果你把這樣一張紙貼在任何寵物的項圈上,一切都會好起來的。任何寵物仍然是動物!相反的過程,即沿著繼承樹向下移動到繼承人,是一個縮小範圍:
public class Main {
public static void main(String[] args) {
WildAnimal wildAnimal = new Coyote();
Coyote coyote = (Coyote) wildAnimal;
coyote.introduce();
}
}
正如您所看到的,這裡我們明確指出我們要將物件轉換為哪個類別。以前我們有一個變量WildAnimal
,現在有Coyote
,它沿著繼承樹往下移動。從邏輯上講,如果沒有明確指示,編譯器不會跳過此類操作,但如果您在括號中指定類型,則一切都會起作用。 讓我們來看另一個更有趣的例子:
public class Main {
public static void main(String[] args) {
Pet pet = new Animal();//error!
}
}
編譯器會拋出錯誤!是什麼原因? 事實上,您正在嘗試將父物件指派給子變數。 換句話說,你想要這樣做:
ДомашнееЖивотное домашнееЖивотное = new Животное();
但如果我們明確指出我們試圖轉換的類型,我們就會成功嗎?數字看起來很有效,讓我們試試看!:)
public class Main {
public static void main(String[] args) {
Pet pet = (Pet) new Animal();
}
}
線程“main”中出現異常 java.lang.ClassCastException:動物無法轉換為 Pet 錯誤!這次編譯器沒有抱怨,但結果我們收到了一個異常。我們已經知道原因:我們正在嘗試將父物件指派給子變數。事實上,為什麼不能這樣做呢? 因為並非所有動物都是寵物。 您創建了一個物件Animal
並嘗試將其指派給一個變數Pet
。但是,例如,土狼也是家畜Animal
,但它不是。Pet
換句話說,當你寫:
Pet pet = (Pet) new Animal();
new Animal()
任何動物都可以 在那裡,而且不一定是家養的!當然,你的變數Pet pet
只適合存放寵物(及其後代),而不適合所有人。因此,對於這種情況,Java 中創建了一個特殊的異常——ClassCastException
轉換類別時出錯。讓我們再說一遍,以便更清楚。父變數(引用)可以指向後代類別的物件:
public class Main {
public static void main(String[] args) {
Pet pet = new Pet();
Animal animal = pet;
Pet pet2 = (Pet) animal;
pet2.introduce();
}
}
例如,我們在這裡不會有任何問題。我們有一個Pet
由連結指向的物件Pet
。然後一個新的連結開始指向同一個物件Animal
。之後我們將其轉換animal
為Pet
. 順便問一下,我們為什麼要這樣做?上次我們有一個例外!因為這次我們的原始物件是Pet pet
!
Pet pet = new Pet();
在前面的範例中,它是一個物件Animal
:
Pet pet = (Pet) new Animal();
後代變數不能分配給祖先物件。相反,你可以做到。
GO TO FULL VERSION