Java中的類型轉換

爲什麼要進行類型轉換?

Java是強類型語言,在強類型語言中變量必須被聲明,而且變量在賦值和運算時也必須類型相同。
在實際應用的時候我們常常需要對不同類型的變量進行操作,因此就必須進行類型轉換。
類型轉換分爲:

  • 自動類型轉換:又叫隱式類型轉換,“隱”就是不需要手動轉換,系統會自動進行類型轉換。
  • 強制類型轉換:又叫顯式類型轉換,“顯”就是需要手動加入語法進行轉換,來提示“這可能是一件危險的事情,信息可能會發生丟失。”強制類型轉換又分爲基本數據類型和引用數據類型兩種

在我看來,類型轉換其實只分爲基本數據類型轉換引用數據類型轉換兩種,下面對這兩種進行分析:


基本數據類型轉換

在學習基本數據類型轉換之前我們需要明確:整數型字面量(例如9)會被JVM默認爲int類型數據,浮點型字面量(例如9.0)會被JVM默認爲double類型數據。

基本數據類型的轉換無非就是兩種:
1.將存儲範圍小的類型轉換成存儲範圍大的類型
2.將存儲範圍大的類型轉換成存儲範圍小的類型

先來看第一種:.將存儲範圍小的類型轉換成存儲範圍大的類型
存儲範圍由小到大爲:byte→short→int→long→float→double
這種類型轉換屬於自動類型轉換,不需要我們進行任何操作,系統會幫助我們完成。範圍小的數據類型可以自動轉換成任一比它範圍大的數據類型。 【這是一種擴展轉換,新類型肯定能容納原來類型的信息,不會造成任何信息丟失,是安全的轉換】
示例代碼:

    byte  b = 20;
    short s = b;
    double d = s;
    System.out.println(d);

最後輸出結果是20.0
byte類型在自動轉換成short類型時,JVM首先會把b轉換成short類型,然後再將b賦值給s。

第二種:將存儲範圍大的類型轉換成存儲範圍小的類型
存儲範圍由大到小爲:double→float→long→int→short→byte
將範圍大的數據類型轉換成範圍小的數據類型需要我們手動添加代碼進行強制類型轉換,這是一種窄化轉換,因爲將能容納更多信息的數據類型轉換成無法容納那麼多信息的類型,就有可能面臨信息丟失的危險,編譯器會強制要求我們進行類型轉換,這實際上是說:“這可能是一件危險的事情,如果無論如何要這麼做,必須顯式的進行類型轉換。”
示例代碼:

 double d = 126.7;
 byte b = (byte) d;
 System.out.println(b);

輸出結果是126,小數位信息發生丟失,這就是強制類型轉換的原因之一:提示我們可能發生信息丟失。

細節補充:

細節一:對float類型的賦值問題:當我們爲float賦值一個小數時,我們會發現編譯器要求我們必須在末尾加上f,例如float f =8.2f;然而當我們爲其賦值一個整數時,我們會發現其末尾可以加f,也可以不加f,例如float f =8;這是爲什麼呢?

解釋:前面已經提到,整數型字面量(例如9)會被JVM默認爲int類型數據,浮點型字面量(例如9.0)會被JVM默認爲double類型數據。也就是說當我們爲float賦值小數時,小數的字面值被JVM默認爲double類型,把double類型賦值給float類型需要進行強制類型轉換,在末尾加上f可以理解成是強制類型轉換的一種方式,所以將小數賦值給float時必須在末尾加上f。當我們爲float賦值整數時,整數的字面值被JVM默認爲int類型,將int類型轉換成float類型會進行自動類型轉換,因此可以不在末尾加f,加上也不會報錯。爲了避免錯誤,最好float型都在末尾加上f。

細節二:對long類型的賦值問題:當我們爲一個long類型賦值時,比如long = 100,此時編譯不會有錯,但是當我們long = 10000000000時發現會報錯,但是long = 10000000000L在末尾加上L後錯誤消失,這是爲什麼?加上L是強制類型轉換嗎?

解釋:當執行long = 100時,100默認爲int類型,int向long進行轉換是自動轉換,因此long = 100不會有任何問題。但是當我們賦值long = 10000000000時,10000000000這個數已經超出了int的表示範圍,但是JVM還是會默認它爲int類型,這已經是一個錯誤了,更談不上賦值給long了,因此需要在其末尾添加L,來告訴編譯器10000000000是一個long類型,將10000000000變回自己本身的類型。當數值超過int型範圍時,必須得加L。
在這裏加L並不是強制類型轉換,而是一種語法約定。爲了避免錯誤,最好long型都在末尾加上L,最好是大寫的L,避免跟數字1混淆。


引用數據類型轉換

(部分參考自:Java引用類型強制轉換

引用數據類型的轉換分爲兩種:

  • 向上轉型:父類引用子類的實例,子類可以自動轉型爲父類。
  • 向下轉型:子類引用父類的實例,父類必須進行強制轉換才能被子類引用。

下面分別進行分析:

>>引用數據類型轉換之向上轉型:

首先給出一個示例:
創建一個父類Animal類,Animal類中包含了一個sleep()方法:


public class Animal {
    public void sleep(){
        System.out.println("This is sleep method!");
    }
}

再創建Animal的子類,Bird類,裏面有bird自己的一個fly()方法:

public class Bird extends Animal {
    private int height;
    public void fly() {
        System.out.println("小鳥飛行高度是:" + height);
    }

}

向上轉型是父類引用子類的實例:Animal animal = new Bird(),子類可以非常自然地轉換成父類。
數據進行類型轉換時總是避免不了一個問題,那就是“安全”,在轉型過程中我們需要清楚這次轉型是否安全,向上轉型只是把引用子類的能力臨時削弱,向上轉型後不可以使用引用子類中新的方法,只能使用父類中所擁有的方法。功能弱的類型引用功能較強的類型是可行並且安全的。

>>引用數據類型轉換之向下轉型

在上面Animal跟Bird類的基礎上,我們來演示向下轉型。
向下轉型是子類引用父類的實例:

 Animal animal = new Bird();//向上轉型
 Bird bird = (Bird) animal;//向下轉型

向下轉型是引用數據類型的強制轉換,向下轉型是一個將向上轉型時子類被削弱的功能還原的一個過程。

但是需要注意的是: 當引用類型的真實身份是父類本身的類型時,強制類型轉換就會產生錯誤!如下面代碼:

Animal animal = new Animal();
Bird bird1 = (Bird) animal;
bird1.fly();

以上代碼編譯時不會出錯,但是運行時會出現Java.lang.ClassCastException錯誤。
因爲這種轉型是不安全的,當我們用一個類型的構造器構造出一個對象時,這個對象的類型就已經確定的,也就說它的本質是不會再發生變化了。Animal類中沒有Bird類中的fly()方法,即使將Animal強制轉換成Bird類,在實際運行中還是找不到animal中的fly()方法。可以理解成“可以說鳥是動物,但是你無法說動物是鳥。”
總起來就是:子類可以自動轉型爲父類,但是父類強制轉換爲子類時只有當引用類型真正的身份爲子類時纔會強制轉換成功,否則失敗。


總結與補充:

1.總結:不管是基本數據類型轉換還是引用數據類型轉換,總結起來就是:『由下往上轉換是自動類型轉換,由上往下轉換是強制類型轉換』[基本數據類型由下往上是由範圍小→範圍大;引用類型由下往上是子類→父類]。但是引用數據強制類型轉換時需要注意限制條件:父類強制轉換爲子類時只有當引用類型真正的身份爲子類時纔會強制轉換成功。
2.補充:向上轉型可以實現多態,即子類對象(實際是地址引用)指向父類這樣可以使代碼複用。例如在寫一個框架時,寫一個類,通過多態,不管以後這個類被繼承出了多少個兒子、孫子、重孫子,這段代碼都不用改變。舉一個例子來進行說明:

1.首先創建一個父類:Animal

public class Animal {
    public void sleep(){
        System.out.println("這是父類中的sleep方法");
    }
}

2.創建子類Bird:重寫父類的sleep方法

public class Bird extends Animal {
    @Override
    public void sleep(){
        System.out.println("Bird已經sleep!");
    }
    }

3.創建子類Dog:也重寫父類的sleep方法

public class Dog extends Animal {
    @Override
    public void sleep(){
        System.out.println("Dog已經sleep!");
    }
}

4.假設我這個框架中封裝了一個類來實現某種動物的睡眠:

public class GoToSleep {
    private Animal animal;
    public GoToSleep(Animal animal){
        this.animal = animal;
    }
    public void ToSleep(){
        animal.sleep();
    }
}

5.當我們要使用這個框架內部類來實現動物的睡眠,比如讓Bird進入睡眠可以這麼寫:

public class Main {
    public static void main(String[] args) {
        Animal animal = new Bird();
        GoToSleep goToSleep = new GoToSleep(animal);
        goToSleep.ToSleep();
    }
}

輸出結果是:
這裏寫圖片描述

當然如果想讓Dog進入睡眠跟Bird是一樣的:

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        GoToSleep goToSleep = new GoToSleep(animal);
        goToSleep.ToSleep();
    }
}

輸出結果爲:
這裏寫圖片描述
通過以上示例,我們會發現利用向上轉型,我們可以複用封裝類GoToSleep,這就是向上轉型的作用之一。

發佈了22 篇原創文章 · 獲贊 23 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章