天天看點

Java動态綁定和靜态綁定(多态)

一、前言引入

綁定

把一個方法與其所在的類/對象 關聯起來叫做方法的綁定。綁定分為靜态綁定(前期綁定)和動态綁定(後期綁定)。

靜态綁定

靜态綁定(前期綁定)是指:在程式運作前就已經知道方法是屬于那個類的,在編譯的時候就可以連接配接到類的中,定位到這個方法。
  • 在Java中,final、private、static修飾的方法以及構造函數都是靜态綁定的,不需程式運作,不需具體的執行個體對象就可以知道這個方法的具體内容。

動态綁定

動态綁定(後期綁定)是指:在程式運作過程中,根據具體的執行個體對象才能具體确定是哪個方法。

動态綁定是多态性得以實作的重要因素,它通過方法表來實作:每個類被加載到虛拟機時,在方法區儲存中繼資料,其中,包括一個叫做 方法表(method table)的東西,表中記錄了這個類定義的方法的指針,每個表項指向一個具體的方法代碼。如果這個類重寫了父類中的某個方法,則對應表項指向新的代碼實作處。

  1. 動态綁定執行過程
  2. 向上轉型
  3. 向下轉型
  4. 總結

二、具體内容

2.1 程式綁定的概念:

綁定指的是一個方法的調用與方法所在的類(方法主體)關聯起來。對java來說,綁定分為靜态綁定和動态綁定;或者叫做前期綁定和後期綁定

靜态綁定:

在程式執行前方法已經被綁定,此時由編譯器或其它連接配接程式實作。例如:C。

針對java簡單的可以了解為程式編譯期的綁定;這裡特别說明一點,java當中的方法隻有final,static,private和構造方法是前期綁定

動态綁定:

後期綁定:在運作時根據具體對象的類型進行綁定。

若一種語言實作了後期綁定,同時必須提供一些機制,可在運作期間判斷對象的類型,并分别調用适當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能自己去調查,找到正确的方法主體。不同的語言對後期綁定的實作方法是有所差別的。但我們至少可以這樣認為:它們都要在對象中安插某些特殊類型的資訊。

2.2 動态綁定的過程:

  • 虛拟機提取對象的實際類型的方法表;
  • 虛拟機搜尋方法簽名;
  • 調用方法。

關于綁定相關的總結:

在了解了三者的概念之後,很明顯我們發現java屬于後期綁定。在java中,幾乎所有的方法都是後期綁定的,在運作時動态綁定方法屬于子類還是基類。但是也有特殊,針對static方法和final方法由于不能被繼承,是以在編譯時就可以确定他們的值,他們是屬于前期綁定的。特别說明的一點是,private聲明的方法和成員變量不能被子類繼承,所有的private方法都被隐式的指定為final的(由此我們也可以知道:将方法聲明為final類型的一是為了防止方法被覆寫,二是為了有效的關閉java中的動态綁定)。java中的後期綁定是有JVM來實作的,我們不用去顯式的聲明它
  • java當中的向上轉型或者說多态是借助于動态綁定實作的,是以了解了動态綁定,也就搞定了向上轉型和多态。而動态綁定的典型發生在父類和子類的轉換聲明之下:
比如:Animal a = new Dog();
           

其具體過程細節如下:

  • 編譯器檢查對象的聲明類型和方法名。假設我們調用 x.fun(args) 方法,并且 x 已經被聲明為 C 類的對象,那麼編譯器會列舉出C類中所有的名稱為 " fun “的方法和從C類的超類繼承過來的” fun "方法
  • 接下來編譯器檢查方法調用中提供的參數類型。如果在所有名稱為" fun " 的方法中有一個參數類型和調用提供的參數類型最為比對,那麼就調用這個方法,這個過程叫做“重載解析”
  • 當程式運作并且使用動态綁定調用方法時,虛拟機必須調用同 x 所指向的對象的實際類型相比對的方法版本。假設實際類型為D(C的子類),如果D類定義了 fun(String) 那麼該方法被調用,否則就在D的超類中搜尋方法 fun(String) ,依次類推

2.3 向上、向下轉型

上面是理論,下面看幾個示例:

class Animal {
	public void eat() {
		System.out.println("吃東西");
	}
}

class Cat extends Animal {
	public void eat() {
		System.out.println("吃貓糧");
	}

	public void playGame() {
		System.out.println("貓捉老鼠");
	}
}

public class Test {
	public static void main(String[] args) {
		Animal a = new Animal();  // 如果沒有向上轉型, 父類引用對象是父類本身,運作會報錯
		Animal a1 = new Cat();  // 向上轉型
		a.eat();
		
		Cat c = (Cat) a1;   // 向下轉型
		c.eat();
		c.playGame();
		
	}
}
           
由于子類重寫了父類的 eat 方法,根據上面的理論知道會去調用子類的e at 方法去執行,因為子類對象有 eat 方法而沒有向上轉型去尋找。把指向子類對象的父類引用 賦給 子類 稱為向下轉型。向下轉型後子類可以調用其特有(擴充)方法 也可以調用 重寫的g本類方法。。。上面的例子中 貓的特征是 抓老鼠,而向上轉型 屏蔽了子類特有的方法playGame,而隻能調用 eat 方法, 但是向下轉型之後就可以調用了。
前面的理論當中已經提到了java的綁定規則,由此可知,在處理java類中的成員變量時,并不是采用運作時綁定,而是一般意義上的靜态綁定。是以在向上轉型的情況下,對象的方法可以找到子類,而對象的屬性還是**父類的屬性。

代碼如下:

public class Father {   
   
  protected String name="父親屬性";   
     
  public void method() {   
    System.out.println("父類方法,對象類型:" + this.getClass());   
  }   
}   
     
public class Son extends Father {   
  protected String name="兒子屬性";   
     
  public void method() {   
    System.out.println("子類方法,對象類型:" + this.getClass());   
  }   
     
  public static void main(String[] args) {   
    Father sample = new Son(); // 向上轉型   
    System.out.println("調用的成員:" + sample.name);   
  }   
}   
           
結論,調用的成員為父親的屬性。
  • 這個結果表明,子類的對象(由父類的引用handle)調用到的是父類的成員變量。是以必須明确,運作時(動态)綁定針對的範疇隻是對象的方法。
弄了半天,向上轉型反而不能擁有子類的全部方法,還得向下轉型,那直接Son s = new Son();豈不是很友善?不知道是不是就我一個開始學習轉型有這種想法。

例子:展現向上轉型的好處,節省代碼。

package com.itheima_03;
    /*
     * 多态的好處:提高了程式的擴充性
     * 		具體展現:定義方法的時候,使用父類型作為參數,将來在使用的時候,使用具體的子類型參與操作。
     * 多态的弊端:不能使用子類的特有功能
     */
    public class DuoTaiDemo {
    	public static void main(String[] args) {
    		AnimalOperator ao = new AnimalOperator();
    		Cat c = new Cat();
    		ao.useAnimal(c);
    		
    		Dog d = new Dog();
    		ao.useAnimal(d);
    		
    		Pig p = new Pig();
    		ao.useAnimal(p);
    	}
    }
    
    
    public class AnimalOperator {
    	/*
    	public void useAnimal(Cat c) { //Cat c = new Cat();
    		c.eat();
    	}
    	
    	public void useAnimal(Dog d) { //Dog d = new Dog();
    		d.eat();
    	}
    	*/
    	
    	public void useAnimal(Animal a) { //Animal a = new Cat();
    		a.eat();
    		//a.lookDoor();
    	}
    }
    
    
   class Cat extends Animal {
    	public void eat() {
    		System.out.println("貓吃魚");
    	}
    }

    
   class Dog extends Animal {
    	public void eat() {
    		System.out.println("狗吃骨頭");
    	}
    	
    	public void lookDoor() {
    		System.out.println("狗看門");
    	}
    }
  
   class Pig extends Animal {
    	public void eat() {
    		System.out.println("豬吃白菜");
    	}
    }
           

上面的簡單例子可以很好地說明多态(向上轉型)帶來的好處大大滴。

3 總結

  • 把子類對象直接賦給父類引用叫upcasting向上轉型,向上轉型不用強制轉型。
Animal a = new Cat();
           
  • 把指向子類對象的父類引用賦給子類引用叫向下轉型(downcasting),要強制轉型,要向下轉型,必須先向上轉型為了安全可以用instanceof判斷。
  • 如father就是一個指向子類對象的父類引用,把father賦給子類引用son 即Son son =(Son)father;
其中father前面的(Son)必須添加,進行強制轉換。向下轉型後子類可以調用其特有(擴充)方法 也可以調用 重寫的方法
  • 向上轉型會丢失(屏蔽)子類特有的方法,但是子類重寫父類的方法,子類方法有效,向上轉型隻能引用父類對象的屬性,要引用子類對象屬性,則要寫getter函數。
  • 向上轉型的作用,減少重複代碼,父類為參數,調有時用子類作為參數,就是利用了向上轉型。這樣使代碼變得簡潔。展現了JAVA的抽象程式設計思想。

繼續閱讀