Java零基礎_學習筆記(十八)Java多態的理解【通俗易懂】

這是我參與8月更文挑戰的第9天,活動詳情查看:8月更文挑戰java

上篇說到Java中的方法覆蓋今天繼續帶你們聊一聊Java多態,不少初學者在自學Java的時候都卡在了多態,多態指的是同一個行爲具備多個不一樣表現形式或形態的能力。編程

是否是感受很抽象,很難理解...不要緊,接下來我來全面的帶你瞭解Java多態,完全搞定它!!!markdown

Java多態

多態(Polymorphism)屬於面向對象三大特徵之一,它的前提是封裝造成獨立體,獨立體之間存在繼承關係,從而產生多態機制。多態是同一個行爲具備多個不一樣表現形式或形態的能力。現實中,好比咱們按下 F1 鍵這個動做:ide

● 若是當前在 Flash 界面下彈出的就是 AS 3 的幫助文檔;svn

● 若是當前在 Word 下彈出的就是 Word 幫助;post

● 若是當前在 Windows 下彈出的就是 Windows 幫助和支持。學習

多態就是「同一個行爲」發生在「不一樣的對象上」會產生不一樣的效果。flex

那麼在java中多態是如何體現的呢?url

在java中容許這樣的兩種語法出現,一種是向上轉型(Upcasting),一種是向下轉型(Downcasting),向上轉型是指子類型轉換爲父類型,又被稱爲自動類型轉換,向下轉型是指父類型轉換爲子類型,又被稱爲強制類型轉換。請看下圖:spa

在java語言中有這樣的一個規定,不管是向上轉型仍是向下轉型,兩種類型之間必需要有繼承關係,沒有繼承關係狀況下進行向上轉型或向下轉型的時候編譯器都會報錯,這一點要死記硬背哦!

接下來咱們來看一段代碼:

public class Animal {
	public void move(){
		System.out.println("Animal move!");
	}
}
複製代碼
public class Cat extends Animal{
	//方法覆蓋
	public void move(){
		System.out.println("走貓步!");
	}
	//子類特有
	public void catchMouse(){
		System.out.println("抓老鼠!");
	}
}
複製代碼
public class Bird extends Animal{
	//方法覆蓋
	public void move(){
		System.out.println("鳥兒在飛翔!");
	}
//子類特有
public void sing(){
	System.out.println("鳥兒在歌唱!");
	}
}
複製代碼
public class Test01 {
	public static void main(String[] args) {
		//建立Animal對象
		Animal a = new Animal();
		a.move();
		//建立Cat對象
		Cat c = new Cat();
		c.move();
		//建立鳥兒對象
		Bird b = new Bird();
		b.move();
	}
}
複製代碼

運行結果以下圖所示:

其實在java中還容許這樣寫代碼,請看:

public class Test02 {
	public static void main(String[] args) {
		Animal a1 = new Cat();
		a1.move();
		Animal a2 = new Bird();
		a2.move();
	}
}
複製代碼

運行結果以下圖所示:

以上程序演示的就是多態,多態就是「同一個行爲(move)」做用在「不一樣的對象上」會有不一樣的表現結果。

java中之因此有多態機制,是由於java容許一個父類型的引用指向一個子類型的對象。也就是說容許這種寫法:Animal a2 = new Bird(),由於Bird is a Animal是可以說通的。

其中Animal a1 = new Cat()或者Animal a2 = new Bird()都是父類型引用指向了子類型對象,都屬於向上轉型(Upcasting),或者叫作自動類型轉換。

我來解釋一下這段代碼片斷【Animal a1 = ****new**** Cat();a1.move(); 】:

java程序包括編譯和運行兩個階段,分析java程序必定要先分析編譯階段,而後再分析運行階段,在編譯階段編譯器只知道a1變量的數據類型是Animal,那麼此時編譯器會去Animal.class字節碼中查找move()方法,發現Animal.class字節碼中存在move()方法,而後將該move()方法綁定到a1引用上,編譯經過了,這個過程咱們能夠理解爲「靜態綁定」階段完成了。

緊接着程序開始運行,進入運行階段,在運行的時候實際上在堆內存中new的對象是Cat類型,也就是說真正在move移動的時候,是Cat貓對象在移動,因此運行的時候就會自動執行Cat類當中的move()方法,這個過程能夠稱爲「動態綁定」。但不管是何時,必須先「靜態綁定」成功以後才能進入「動態綁定」階段。

來看如下的一段代碼以及編譯結果:

public class Test03 {
	public static void main(String[] args) {
		Animal a = new Cat();
		a.catchMouse();
	}
}
複製代碼

編譯結果:

有人認爲Cat貓是能夠抓老鼠的呀,爲何會編譯報錯呢?

那是由於「Animal a = ****new**** Cat();」在編譯的時候,編譯器只知道a變量的數據類型是Animal,也就是說它只會去Animal.class字節碼中查找catchMouse()方法,結果沒找到,天然「靜態綁定」就失敗了,編譯沒有經過。就像以上描述的錯誤信息同樣:在類型爲Animal的變量a中找不到方法catchMouse()。

那麼,假如說我就是想讓這隻貓去抓老鼠,以上代碼應該如何修改呢?

請看如下代碼:

public class Test04 {
	public static void main(String[] args) {
		//向上轉型
		Animal a = new Cat();
		//向下轉型:爲了調用子類對象特有的方法
		Cat c = (Cat)a;
		c.catchMouse();
	}
}
複製代碼

運行結果以下圖所示:

咱們能夠看到直接使用a引用是沒法調用catchMouse()方法的,由於這個方法屬於子類Cat中特有的行爲,不是全部Animal動物均可以抓老鼠的,要想讓它去抓老鼠,就必須作向下轉型(Downcasting),也就是使用強制類型轉換將Animal類型的a引用轉換成Cat類型的引用c(Cat c = (Cat)a;),使用Cat類型的c引用調用catchMouse()方法。

經過這個案例,能夠得出:只有在訪問子類型中特有數據的時候,須要先進行向下轉型。其實向下轉型就是用在這種情形之下。

那麼向下轉型會存在什麼風險嗎?請看如下代碼:

public class Test05 {
	public static void main(String[] args) {
		Animal a = new Bird();
		Cat c = (Cat)a;
	}
}
複製代碼

以上代碼能夠編譯經過嗎?答案是能夠的,爲何呢?

那是由於編譯器只知道a變量是Animal類型,Animal類和Cat類之間存在繼承關係,因此能夠進行向下轉型(前面提到過,只要兩種類型之間存在繼承關係,就能夠進行向上或向下轉型),語法上沒有錯誤,因此編譯經過了。可是運行的時候會出問題嗎,由於畢竟a引用指向的真實對象是一隻小鳥。來看運行結果:

以上的異常是很常見的ClassCastException,翻譯爲類型轉換異常,這種異常一般出如今向下轉型的操做過程中,當類型不兼容的狀況下進行轉型出現的異常,之因此出現此異常是由於在程序運行階段a引用指向的對象是一隻小鳥,而後咱們要將一隻小鳥轉換成一隻貓,這顯然是不合理的,由於小鳥和貓之間是沒有繼承關係的。爲了不這種異常的發生,建議在進行向下轉型以前進行運行期類型判斷,這就須要咱們學習一個運算符了,它就是instanceof。

instanceof運算符的語法格式是這樣的:

(引用 instanceof 類型)

instanceof運算符的運算結果是布爾類型,多是true,也多是false,假設(c instanceof Cat)結果是true則表示在運行階段c引用指向的對象是Cat類型,若是結果是false則表示在運行階段c引用指向的對象不是Cat類型。有了instanceof運算符,向下轉型就能夠這樣寫了:

public class Test05 {
	public static void main(String[] args) {
		Animal a = new Bird();
		if(a instanceof Cat){
			Cat c = (Cat)a;
			c.catchMouse();
		}
	}
}
複製代碼

以上程序運行以後再也不發生異常,而且什麼也沒有輸出,那是由於if語句的條件並無成立,由於在運行階段a引用指向的對象不是Cat類型,因此(a instanceof Cat)是false,天然就不會進行向下轉型了,也不會出現ClassCastException異常了。

在實際開發中,java中有這樣一條默認的規範須要你們記住:在進行任何向下轉型的操做以前,要使用instanceof進行判斷,這是一個很好的編程習慣。就像下面的代碼:

public class Test05 {
	public static void main(String[] args) {
		Animal a = new Bird();
		if(a instanceof Cat){
			Cat c = (Cat)a;
			c.catchMouse();
		}else if(a instanceof Bird){
			Bird b = (Bird)a;
			b.sing();
		}
	}
}
複製代碼

運行結果以下圖所示:

圖示:向下轉型前判斷

到這裏你們理解什麼是多態了嗎?其實多態存在的三個必要條件分別是:

● 繼承

● 方法覆蓋

● 父類型引用指向子類型對象

多態顯然是離不開方法覆蓋機制的,多態就是由於編譯階段綁定父類當中的方法,程序運行階段自動調用子類對象上的方法,若是子類對象上的方法沒有進行重寫,這個時候建立子類對象就沒有意義了,天然多態也就沒有意義了,只有子類將方法重寫以後調用到子類對象上的方法產生不一樣效果時,多態就造成了。實際上方法覆蓋機制和多態機制是捆綁的,誰也離不開誰,多態離不開方法覆蓋,方法覆蓋離開了多態也就沒有意義了。

接下里就來看看以前沒有解決的問題:方法覆蓋主要是說實例方法,靜態方法爲何不談方法覆蓋?

public class OverrideTest {
	public static void main(String[] args) {
		Math.sum();
		MathSubClass.sum();
	}
}
複製代碼
public class Math{
	public static void sum(){
		System.out.println("Math's sum execute!");
	}
}
複製代碼
public class MathSubClass extends Math{
	//嘗試覆蓋從父類中繼承過來的靜態方法
	public static void sum(){
		System.out.println("MathSubClass's sum execute!");
	}
}
複製代碼

運行結果以下圖所示:

圖示:嘗試覆蓋靜態方法

咱們發現貌似也發生了覆蓋,在程序運行的時候確實也調用了「子類MathSubClass」的sum方法,但這種「覆蓋」有意義嗎?其實上面的課程咱們已經說過了,方法覆蓋和多態機制聯合起來纔有意義,咱們來看看這種「覆蓋」是否可以達到「多態」的效果,請看代碼:

public class OverrideTest {
	public static void main(String[] args) {
		Math m = new MathSubClass();
		m.sum();
		m = null;
		m.sum();
	}
}
複製代碼

運行結果以下圖所示:

經過以上的代碼,咱們發現雖然建立了子類型對象「****new**** MathSubClass()」,可是程序在運行的時候仍然調用的是Math類當中的sum方法,甚至m = null的時候再去調用m.sum()也沒有出現空指針異常,這說明靜態方法的執行壓根和對象無關,既然和對象無關那就表示和多態無關,既然和多態無關,也就是說靜態方法的「覆蓋」是沒有意義的,因此一般咱們不談靜態方法的覆蓋。

相關文章
相關標籤/搜索