欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Java多態(tài)對象的類型轉(zhuǎn)換與動態(tài)綁定

 更新時間:2015年09月26日 18:28:57   投稿:goldensun  
這篇文章主要介紹了詳解Java多態(tài)對象的類型轉(zhuǎn)換與動態(tài)綁定,是Java入門學習中的基礎知識,需要的朋友可以參考下

Java多態(tài)對象的類型轉(zhuǎn)換
這里所說的對象類型轉(zhuǎn)換,是指存在繼承關(guān)系的對象,不是任意類型的對象。當對不存在繼承關(guān)系的對象進行強制類型轉(zhuǎn)換時,java 運行時將拋出 java.lang.ClassCastException 異常。

在繼承鏈中,我們將子類向父類轉(zhuǎn)換稱為“向上轉(zhuǎn)型”,將父類向子類轉(zhuǎn)換稱為“向下轉(zhuǎn)型”。

很多時候,我們會將變量定義為父類的類型,卻引用子類的對象,這個過程就是向上轉(zhuǎn)型。程序運行時通過動態(tài)綁定來實現(xiàn)對子類方法的調(diào)用,也就是多態(tài)性。

然而有些時候為了完成某些父類沒有的功能,我們需要將向上轉(zhuǎn)型后的子類對象再轉(zhuǎn)成子類,調(diào)用子類的方法,這就是向下轉(zhuǎn)型。

注意:不能直接將父類的對象強制轉(zhuǎn)換為子類類型,只能將向上轉(zhuǎn)型后的子類對象再次轉(zhuǎn)換為子類類型。也就是說,子類對象必須向上轉(zhuǎn)型后,才能再向下轉(zhuǎn)型。請看下面的代碼:

public class Demo {
  public static void main(String args[]) {
    SuperClass superObj = new SuperClass();
    SonClass sonObj = new SonClass();
    // 下面的代碼運行時會拋出異常,不能將父類對象直接轉(zhuǎn)換為子類類型
    // SonClass sonObj2 = (SonClass)superObj;
    // 先向上轉(zhuǎn)型,再向下轉(zhuǎn)型
    superObj = sonObj;
    SonClass sonObj1 = (SonClass)superObj;
  }
}
class SuperClass{ }
class SonClass extends SuperClass{ } 

將第7行的注釋去掉,運行時會拋出異常,但是編譯可以通過。

因為向下轉(zhuǎn)型存在風險,所以在接收到父類的一個引用時,請務必使用 instanceof 運算符來判斷該對象是否是你所要的子類,請看下面的代碼:

public class Demo {
  public static void main(String args[]) {
    SuperClass superObj = new SuperClass();
    SonClass sonObj = new SonClass();
    // superObj 不是 SonClass 類的實例
    if(superObj instanceof SonClass){
      SonClass sonObj1 = (SonClass)superObj;
    }else{
      System.out.println("①不能轉(zhuǎn)換");
    }
    superObj = sonObj;
    // superObj 是 SonClass 類的實例
    if(superObj instanceof SonClass){
      SonClass sonObj2 = (SonClass)superObj;
    }else{
      System.out.println("②不能轉(zhuǎn)換");
    }
  }
}
class SuperClass{ }
class SonClass extends SuperClass{ }

運行結(jié)果:

①不能轉(zhuǎn)換

總結(jié):對象的類型轉(zhuǎn)換在程序運行時檢查,向上轉(zhuǎn)型會自動進行,向下轉(zhuǎn)型的對象必須是當前引用類型的子類。

Java多態(tài)和動態(tài)綁定
在Java中,父類的變量可以引用父類的實例,也可以引用子類的實例。

請讀者先看一段代碼:

public class Demo {
  public static void main(String[] args){
    Animal obj = new Animal();
    obj.cry();
    obj = new Cat();
    obj.cry();
    obj = new Dog();
    obj.cry();
  }
}
class Animal{
  // 動物的叫聲
  public void cry(){
    System.out.println("不知道怎么叫");
  }
  
}
class Cat extends Animal{
  // 貓的叫聲
  public void cry(){
    System.out.println("喵喵~");
  }
}
class Dog extends Animal{
  // 狗的叫聲
  public void cry(){
    System.out.println("汪汪~");
  }
}

運行結(jié)果:

不知道怎么叫
喵喵~
汪汪~

上面的代碼,定義了三個類,分別是 Animal、Cat 和 Dog,Cat 和 Dog 類都繼承自 Animal 類。obj 變量的類型為 Animal,它既可以指向 Animal 類的實例,也可以指向 Cat 和 Dog 類的實例,這是正確的。也就是說,父類的變量可以引用父類的實例,也可以引用子類的實例。注意反過來是錯誤的,因為所有的貓都是動物,但不是所有的動物都是貓。

可以看出,obj 既可以是人類,也可以是貓、狗,它有不同的表現(xiàn)形式,這就被稱為多態(tài)。多態(tài)是指一個事物有不同的表現(xiàn)形式或形態(tài)。

再比如“人類”,也有很多不同的表達或?qū)崿F(xiàn),TA 可以是司機、教師、醫(yī)生等,你憎恨自己的時候會說“下輩子重新做人”,那么你下輩子成為司機、教師、醫(yī)生都可以,我們就說“人類”具備了多態(tài)性。

多態(tài)存在的三個必要條件:要有繼承、要有重寫、父類變量引用子類對象。

當使用多態(tài)方式調(diào)用方法時:
首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,則檢查子類是否覆蓋了該方法。
如果子類覆蓋了該方法,就調(diào)用子類的方法,否則調(diào)用父類方法。

從上面的例子可以看出,多態(tài)的一個好處是:當子類比較多時,也不需要定義多個變量,可以只定義一個父類類型的變量來引用不同子類的實例。請再看下面的一個例子:

public class Demo {
  public static void main(String[] args){
    // 借助多態(tài),主人可以給很多動物喂食
    Master ma = new Master();
    ma.feed(new Animal(), new Food());
    ma.feed(new Cat(), new Fish());
    ma.feed(new Dog(), new Bone());
  }
}
// Animal類及其子類
class Animal{
  public void eat(Food f){
    System.out.println("我是一個小動物,正在吃" + f.getFood());
  }
}
class Cat extends Animal{
  public void eat(Food f){
    System.out.println("我是一只小貓咪,正在吃" + f.getFood());
  }
}
class Dog extends Animal{
  public void eat(Food f){
    System.out.println("我是一只狗狗,正在吃" + f.getFood());
  }
}
// Food及其子類
class Food{
  public String getFood(){
    return "事物";
  }
}
class Fish extends Food{
  public String getFood(){
    return "魚";
  }
}
class Bone extends Food{
  public String getFood(){
    return "骨頭";
  }
}
// Master類
class Master{
  public void feed(Animal an, Food f){
    an.eat(f);
  }
}

運行結(jié)果:

我是一個小動物,正在吃事物
我是一只小貓咪,正在吃魚
我是一只狗狗,正在吃骨頭

Master 類的 feed 方法有兩個參數(shù),分別是 Animal 類型和 Food 類型,因為是父類,所以可以將子類的實例傳遞給它,這樣 Master 類就不需要多個方法來給不同的動物喂食。
動態(tài)綁定

為了理解多態(tài)的本質(zhì),下面講一下Java調(diào)用方法的詳細流程。

1) 編譯器查看對象的聲明類型和方法名。

假設調(diào)用 obj.func(param),obj 為 Cat 類的對象。需要注意的是,有可能存在多個名字為func但參數(shù)簽名不一樣的方法。例如,可能存在方法 func(int) 和 func(String)。編譯器將會一一列舉所有 Cat 類中名為func的方法和其父類 Animal 中訪問屬性為 public 且名為func的方法。

這樣,編譯器就獲得了所有可能被調(diào)用的候選方法列表。

2) 接下來,編澤器將檢查調(diào)用方法時提供的參數(shù)簽名。

如果在所有名為func的方法中存在一個與提供的參數(shù)簽名完全匹配的方法,那么就選擇這個方法。這個過程被稱為重載解析(overloading resolution)。例如,如果調(diào)用 func("hello"),編譯器會選擇 func(String),而不是 func(int)。由于自動類型轉(zhuǎn)換的存在,例如 int 可以轉(zhuǎn)換為 double,如果沒有找到與調(diào)用方法參數(shù)簽名相同的方法,就進行類型轉(zhuǎn)換后再繼續(xù)查找,如果最終沒有匹配的類型或者有多個方法與之匹配,那么編譯錯誤。

這樣,編譯器就獲得了需要調(diào)用的方法名字和參數(shù)簽名。

3) 如果方法的修飾符是private、static、final(static和final將在后續(xù)講解),或者是構(gòu)造方法,那么編譯器將可以準確地知道應該調(diào)用哪個方法,我們將這種調(diào)用方式 稱為靜態(tài)綁定(static binding)。

與此對應的是,調(diào)用的方法依賴于對象的實際類型, 并在運行時實現(xiàn)動態(tài)綁。例如調(diào)用 func("hello"),編澤器將采用動態(tài)綁定的方式生成一條調(diào)用 func(String) 的指令。

4)當程序運行,并且釆用動態(tài)綁定調(diào)用方法時,JVM一定會調(diào)用與 obj 所引用對象的實際類型最合適的那個類的方法。我們已經(jīng)假設 obj 的實際類型是 Cat,它是 Animal 的子類,如果 Cat 中定義了 func(String),就調(diào)用它,否則將在 Animal 類及其父類中尋找。

每次調(diào)用方法都要進行搜索,時間開銷相當大,因此,JVM預先為每個類創(chuàng)建了一個方法表(method lable),其中列出了所有方法的名稱、參數(shù)簽名和所屬的類。這樣一來,在真正調(diào)用方法的時候,虛擬機僅查找這個表就行了。在上面的例子中,JVM 搜索 Cat 類的方法表,以便尋找與調(diào)用 func("hello") 相匹配的方法。這個方法既有可能是 Cat.func(String),也有可能是 Animal.func(String)。注意,如果調(diào)用super.func("hello"),編譯器將對父類的方法表迸行搜索。

假設 Animal 類包含cry()、getName()、getAge() 三個方法,那么它的方法表如下:
cry() -> Animal.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()

實際上,Animal 也有默認的父類 Object(后續(xù)會講解),會繼承 Object 的方法,所以上面列舉的方法并不完整。

假設 Cat 類覆蓋了 Animal 類中的 cry() 方法,并且新增了一個方法 climbTree(),那么它的參數(shù)列表為:
cry() -> Cat.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()
climbTree() -> Cat.climbTree()

在運行的時候,調(diào)用 obj.cry() 方法的過程如下:
JVM 首先訪問 obj 的實際類型的方法表,可能是 Animal 類的方法表,也可能是 Cat 類及其子類的方法表。
JVM 在方法表中搜索與 cry() 匹配的方法,找到后,就知道它屬于哪個類了。
JVM 調(diào)用該方法。

相關(guān)文章

  • 五種Java多線程同步的方法

    五種Java多線程同步的方法

    這篇文章主要為大家詳細介紹了五種Java多線程同步的方法,需要的朋友可以參考下
    2015-09-09
  • java裁剪圖片并保存的示例分享

    java裁剪圖片并保存的示例分享

    在這篇文章中我們將學習如何用Java 對圖像進行剪裁并將剪裁出來的部分單獨保存到文件中
    2014-01-01
  • Springboot項目引入druid安裝部署使用教程

    Springboot項目引入druid安裝部署使用教程

    這篇文章主要介紹了Springboot項目引入druid安裝部署使用,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-01-01
  • SpringBoot整合Xxl-job實現(xiàn)定時任務的全過程

    SpringBoot整合Xxl-job實現(xiàn)定時任務的全過程

    XXL-JOB是一個分布式任務調(diào)度平臺,其核心設計目標是開發(fā)迅速、學習簡單、輕量級、易擴展,下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合Xxl-job實現(xiàn)定時任務的相關(guān)資料,需要的朋友可以參考下
    2022-01-01
  • MyBatis+MyBatisPlus中遇到的一些坑及解決

    MyBatis+MyBatisPlus中遇到的一些坑及解決

    這篇文章主要介紹了MyBatis+MyBatisPlus中遇到的一些坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • 基于Security實現(xiàn)OIDC單點登錄的詳細流程

    基于Security實現(xiàn)OIDC單點登錄的詳細流程

    本文主要是給大家介紹 OIDC 的核心概念以及如何通過對 Spring Security 的授權(quán)碼模式進行擴展來實現(xiàn) OIDC 的單點登錄。對Security實現(xiàn)OIDC單點登錄的詳細過程感興趣的朋友,一起看看吧
    2021-09-09
  • Java中Gson的使用詳解

    Java中Gson的使用詳解

    這篇文章主要介紹了Java中Gson的使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-12-12
  • Spring Security自定義認證器的實現(xiàn)代碼

    Spring Security自定義認證器的實現(xiàn)代碼

    這篇文章主要介紹了Spring Security自定義認證器的實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06
  • IDEA中JDK是1.8但Java版本只有21和17的解決辦法

    IDEA中JDK是1.8但Java版本只有21和17的解決辦法

    JDK 1.8(Java Development Kit 1.8)是Java平臺的一個版本,它包含了用于開發(fā)和運行Java應用程序的工具和庫,下面這篇文章主要給大家介紹了關(guān)于IDEA中JDK是1.8但Java版本只有21和17的解決辦法,需要的朋友可以參考下
    2024-01-01
  • Java整合騰訊云短信發(fā)送實例代碼

    Java整合騰訊云短信發(fā)送實例代碼

    大家好,本篇文章主要講的是Java整合騰訊云短信發(fā)送實例代碼,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12

最新評論