java學習(十一)——多態、方法調用

多態

對於某一個對象(事物),在不同的時刻體現出來的不同狀態叫做多態

如:    水的液態、固態和氣態
            學生在上課時間上課,在吃飯時間吃飯,在休息的時候睡覺

在java中,對象變量是多態的,一個超類對象除了可以引用一個自身類對象,還可以引用它的派生類對象。通過繼承和方法重寫來實現多態。

對象間的轉型問題

在java中,每個對象變量都屬於一個類型,類型描述了這個變量所引用的以及能夠引用的對象類型, 將一個值存入變量時,編譯器會檢查是否允許該操作

1.向上轉型將一個子類的引用賦給一個超類變量,編譯器是允許的,不用進行強制類型轉換。

                格式:    超類 超類變量=new 子類();

2. 向下轉型但是將一個超類的引用賦給 一個子類變量,必須進行強制類型轉換,這樣才能夠通過運行時的檢查

                格式:    子類 子類對象變量名=(子類)父類對象引用

class Animal
{
	public int age=40;

	public void eat()
	{
		System.out.println("吃東西");
	}
	public void sleep()
	{
		System.out.println("睡覺");
	}
} 

class Cat extends Animal
{
	public int age=5;
	//重寫了父類的方法
	public void eat()
	{
		System.out.println("貓吃老鼠");	
	}
	public void playGame()
	{
		System.out.println("玩毛線球");
	}
}
class Dog extends Animal
{
	public int age=3;
	//重寫了父類的方法
	public void eat()
	{
		System.out.println("狗啃骨頭");	
	}
}
class AnimalTest
{
	public static void main(String[] args)
	{
		
		//向上轉型:不需要強制類型轉換
		Animal a=new Cat();
		System.out.println(a.age);	//調用的是父類的成員變量
		a.eat();					//調用的是子類Cat的方法
		//a.playGame();				//多態的弊端:無法訪問子類特有的方法		error:類型爲Animal的變量a找不到方法playGame()
		a.sleep();					//調用的是子類Cat的方法

		/*
			結論:當超類類變量引用子類對象
				訪問成員變量時:訪問的是父類的成員變量
				訪問成員方法時:先訪問的是超類的此方法,如果此方法被子類覆蓋,則調用子類的方法。並且不能訪問子類特有的方法
		*/
		System.out.println("------------");
		//向下轉型:需要強制類型轉換
		Cat cat=(Cat)a;
		System.out.println(cat.age);	//訪問的是子類Cat的成員變量
		cat.eat();					//訪問的是子類Cat的方法
		cat.playGame();				//可以訪問子類Cat特有的方法
		//Cat cat2=a;				//error:Animal無法轉換爲Cat

		System.out.println("------------");
		//Dog dog=(Dog)a;		
		/*上條語句在編譯時並不會出現錯誤,這是因爲編譯器認爲超類變量a可能是Dog類,但是在運行時會拋出一個ClassCastException(類型轉換異常)
		如果沒有捕獲這個異常,那麼程序就會異常終止,如何解決這個問題呢?
			在進行類型轉換之前,先查看是否能夠成功的轉換,通過使用instanceof操作符來實現。如下
		*/
		if(a instanceof Dog)
		{
			Dog dog=(Dog)a;	
			System.out.println("可以進行轉換");
		}
		else
		{
			System.out.println("不可以進行轉換");	
		}

        System.out.println("-----------instanceof 操作符-----------------");
        System.out.println(a instanceof Dog);
        System.out.println(a instanceof Cat);
        System.out.println(null instanceof Cat);
        System.out.println(a instanceof Animal);
	}
}

 instanceof (二元)操作符:

 boolean result=Object instanceof class

 參數:

  • Object: 必選。任意對象表達式
  • class: 必選。任意已定義的對象類

含義如果Object是class的一個實例,則返回true,如果Object不是class的一個實例或者爲null時,返回false

運行結果

 多態的好處:

1. 應用程序不必爲每一個派生類編寫功能調用,只需要對抽象超類進行處理即可。提高了程序的可複用性。(通過繼承來實現)
2. 派生類的功能可以被超類的方法或引用變量所調用,這叫向後兼容,可以提高可擴充性和可維護性。

多態的弊端:不能使用子類特有的方法

下面的例子可以看出使用多態時,在使用不同的Animal子類時,都利用了同一個Tool.eat()方法,提高了程序的複用性。同時,在創建新的子類時,也可以繼續使用Tool.eat()方法而不用進行修改,因此使用多態可以提高代碼的可擴充性和可維護性。

但是當使用父類對象變量引用子類對象時,派生類Cat特有的playGame()方法不能被調用。

class Animal
{
	public int age=40;

	public void eat()
	{
		System.out.println("吃東西");
	}
	public void sleep()
	{
		System.out.println("在晚上睡覺");
	}
} 

class Cat extends Animal
{
	public int age=5;
	//重寫了父類的方法
	public void eat()
	{
		System.out.println("貓吃老鼠");	
	}
	public void playGame()
	{
		System.out.println("貓喜歡玩毛線球");
	}
}
class Dog extends Animal
{
	public int age=3;
	//重寫了父類的方法
	public void eat()
	{
		System.out.println("狗啃骨頭");	
	}
}
class Pig extends Animal
{
	public void eat()
	{
		System.out.println("豬吃白菜");
	}
}
class Tool
{
	public static void eat(Animal animal)
	{
		animal.eat();
		animal.sleep();
	}
}
class AnimalTest2
{
	public static void main(String[] args)
	{
		Cat cat=new Cat();
		Tool.eat(cat);

		Dog dog=new Dog();
		Tool.eat(dog);

		Pig pig=new Pig();
		Tool.eat(pig);

		System.out.println("-------------------");
		Animal a=new Cat();
		//a.playGame();		//error:類型爲Animal 的變量a 找不到方法 playGame();
		Tool.eat(a);
	}
}

運行結果 

多態的其他實現方式:

  1. 接口
  2. 抽象類和抽象方法

方法調用

如何調用方法呢?假設要調用x.f(args),Son是Father的子類

1.編譯器查看對象的聲明類型(即隱式參數類型)和方法名,獲得所有可能被調用的候選方法。注:有可能存在着方法的重載,即有多個參數類型不一樣的方法編譯器會列舉Son類中所有名爲f的方法和其超類中所有的非私有的名爲f方法,(超類的私有方法不可訪問)

2.編譯器查看調用方法時提供的參數類型。獲得方法名字和參數類型。如果在所有名爲f的方法中存在一個方法的參數類型與調用方法提供的實參類型匹配,就選擇這個方法,這個過程稱爲重載解析。這個過程是比較複雜的,如要考慮到參數類型轉換,如int 轉換爲double,Son轉換爲Father。如果編譯器沒有找到與參數類型相匹配的方法,或者發現經過類型轉換後有多個方法與之匹配,就會報告一個錯誤。

靜態綁定:如果是private方法、static方法、final方法或者構造器。那麼編譯器將可以準確的知道應該調用那個方法,我們將這種調用方式稱爲靜態綁定。

動態綁定:調用的方法依賴於隱式參數的實際類型,並且在運行時實現調用那個方法,我們稱這種調用方式爲動態綁定。

當程序運行,並採用動態綁定調用方法時,虛擬機一定調用與x所引用對象的實際類型最合適那個類的方法,假設x的實際類型是Son,它是Father的子類,如果Son類定義了方法f(String),就直接調用它,否則將在Son類的超類Father中尋找f(String),依次類推

方法表(method table):每次調用方法都要進行搜索,時間開銷非常大,因此,虛擬機事先爲每個類創建了一個方法表(method table)。其中列出了所有方法的簽名和實際調用的方法。這樣一來,在真正的調用方法的時候,虛擬機僅查找這個表就行了。

完整的過程;

Father s=new Father的子類();       //這裏Son還可以替換爲其他Father的子類

s.show();

        1.編譯器查看s的聲明類型爲Father
        2
.編譯器將Father類方法表和其超類中所有的名爲show的方法列舉出來
        3.
編譯器查看調用方法時提供的參數類型。進行重載解析,如果在所有名爲show的方法中存在一個與提供的參數類型完全匹配, 就選擇這個方法。至此這個方法就已經找到了。
        4.判斷該方法是靜態綁定還是動態綁定。
          
 如果該方法是private方法、final方法或構造器,java虛擬機就調用這個方法。調用方法結束。
            否則,在程序運行過程中,虛擬機提取s引用對象的實際類型的方法表。有可能是Son的方法表,也有可能是Father其他子類的方法表
        5.假設s引用對象的實際類型是Son.
      
     如果Son類定義了該方法,虛擬機就直接調用它。
            否則在Son類的超類中尋找show()方法,依次類推。
  

 

返回目錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章