1. 程式人生 > >java面向物件 多型

java面向物件 多型

1. 多型概述

多型是繼封裝、繼承之後,面向物件的第三大特性。
現實事物經常會體現出多種形態,如學生,學生是人的一種,則一個具體的同學張三既是學生也是
人,即出現兩種形態

Java 作為面向物件的語言,同樣可以描述一個事物的多種形態。如 Student 類繼承了 Person 類,一
個 Student 的物件便既是 Student,又是 Person。
           Java 中多型的程式碼體現在一個子類物件(實現類物件)既可以給這個子類(實現類物件)引用變數賦值,
又可以給這個子類(實現類物件)的父類(介面)變數賦值。

如 Student 類可以為 Person 類的子類。那麼一個 Student 物件既可以賦值給一個 Student 型別的引用,
也可以賦值給一個 Person 型別的引用。

最終多型體現為父類引用變數可以指向子類物件。Person p=new Student();
多型的前提是必須有子父類關係或者類實現介面關係,否則無法完成多型。
在使用多型後的父類引用變數呼叫方法時,會呼叫子類重寫後的方法.

2.多型的定義與使用格式 

多型的定義格式:就是父類的引用變數指向子類物件

父類型別 變數名 = new 子類型別();
變數名.方法名();

⚫ 普通類多型定義的格式

父類 變數名 = new 子類();
如: class Fu {}
class Zi extends Fu {}
//類的多型使用
Fu f = new Zi(); 

⚫ 注意事項
同一個父類的方法會被不同的子類重寫。在呼叫方法時,呼叫的為各個子類重寫後的方法。

如 Person p1 = new Student();

Person p2 = new Teacher();

p1.work(); //p1 會呼叫 Student 類中重寫的 work 方法

p2.work(); //p2 會呼叫 Teacher 類中重寫的 work

當變數名指向不同的子類物件時,由於每個子類重寫父類方法的內容不同,所以會呼叫不同的方法 

 

3. 多型-成員的特點


掌握了多型的基本使用後,那麼多型出現後類的成員有啥變化呢?前面學習繼承時,我們知道子父
類之間成員變數有了自己的特定變化,那麼當多型出現後,成員變數在使用上有沒有變化呢?


a.多型出現後會導致子父類中的成員變數有微弱的變化。看如下程式碼



class Fu {
int num = 4;
}

class Zi extends Fu {
int num = 5;
}

public class Maopao {

	public static void main(String[] args) {
		Fu f = new Zi();
		System.out.println(f.num);//4
		Zi z = new Zi();
		System.out.println(z.num);//5
	}

}

多型成員變數
當子父類中出現同名的成員變數時,多型呼叫該變數時:
編譯時期:參考的是引用型變數所屬的類中是否有被呼叫的成員變數。沒有,編譯失敗
執行時期:也是呼叫引用型變數所屬的類中的成員變數。
簡單記:編譯和執行都參考等號的左邊。編譯執行看左邊。

多型出現後會導致子父類中的成員方法有微弱的變化。看如下程式碼


class Fu {
	int num = 4;

	void show() {
		System.out.println("Fu show num");
	}
}

class Zi extends Fu {
	int num = 5;
	void show() {
		System.out.println("Zi show num");
		}

}

public class Demo {

	public static void main(String[] args) {
		Fu f = new Zi();
		System.out.println(f.num);//4
                f.show();//"Zi show num"
		Zi z = new Zi();
		
		System.out.println(z.num);
		
	}

}

多型成員方法
編譯時期:參考引用變數所屬的類,如果沒有類中沒有呼叫的方法,編譯失敗
執行時期:參考引用變數所指的物件所屬的類,並執行物件所屬類中的成員方法。
簡而言之:編譯看左邊,執行看右邊。

4 .instanceof 關鍵字

我們可以通過 instanceof 關鍵字來判斷某個物件是否屬於某種資料型別。如學生的物件屬於學生類,
學生的物件也屬於人類。
使用格式:
boolean b = 物件 instanceof 資料型別;

Person p1 = new Student(); // 前提條件,學生類已經繼承了人類
boolean flag = p1 instanceof Student; //flag 結果為 true
boolean flag2 = p2 instanceof Teacher; //flag 結果為 false

 5. 多型-轉型

多型的轉型分為向上轉型與向下轉型兩種:
⚫ 向上轉型:當有子類物件賦值給一個父類引用時,便是向上轉型,多型本身就是向上轉型的過
程。
使用格式

父類型別 變數名 = new 子類型別()

如:Person p = new Student();

⚫ 向下轉型:一個已經向上轉型的子類物件可以使用強制型別轉換的格式,將父類引用轉為子類
引用,這個過程是向下轉型。如果是直接建立父類物件,是無法向下轉型的!
使用格式:

子類型別 變數名 = (子類型別) 父類型別的變數;

Person p = new Persion();
如:Student stu = (Student) p; //變數 p 實際上指向 Student 物件 

6 多型的好處與弊端 

當父類的引用指向子類物件時,就發生了向上轉型,即把子類型別物件轉成了父類型別。向上轉型
的好處是隱藏了子類型別,提高了程式碼的擴充套件性。
但向上轉型也有弊端,只能使用父類共性的內容,而無法使用子類特有功能,功能有限制。

//描述動物類,並抽取共性 eat 方法
abstract class Animal {
	abstract void eat();
}

//描述狗類,繼承動物類,重寫 eat 方法,增加 lookHome 方法
class Dog extends Animal {
	void eat() {
		System.out.println("啃骨頭");
	}

	void lookHome() {
		System.out.println("看家");
	}
}

//描述貓類,繼承動物類,重寫 eat 方法,增加 catchMouse 方法
class Cat extends Animal {
	void eat() {
		System.out.println("吃魚");
	}

	void catchMouse() {
		System.out.println("抓老鼠");
	}
}

public class Test {
	public static void main(String[] args) {
		Animal a = new Dog(); // 多型形式,建立一個狗物件
		a.eat(); // 呼叫物件中的方法,會執行狗類中的 eat 方法
// a.lookHome();//使用 Dog 類特有的方法,需要向下轉型,不能直接使用
//為了使用狗類的 lookHome 方法,需要向下轉型
// 向下轉型過程中,可能會發生型別轉換的錯誤,即 ClassCastException 異常
//那麼,在轉之前需要做健壯性判斷
		if (!a instanceof Dog) { // 判斷當前物件是否是 Dog 型別
			System.out.println("型別不匹配,不能轉換");
			return;
		}
		Dog d = (Dog) a; // 向下轉型
		d.lookHome();// 呼叫狗類的 lookHome 方法
	}
}

 總結一下:
⚫ 什麼時候使用向上轉型:
當不需要面對子類型別時,通過提高擴充套件性,或者使用父類的功能就能完成相應的操作,這時
就可以使用向上轉型。

如:Animal a = new Dog();
 a.eat();

⚫ 什麼時候使用向下轉型
當要使用子類特有功能時,就需要使用向下轉型。

如:Dog d = (Dog) a; //向下轉型
 d.lookHome();//呼叫狗類的 lookHome 方法

 ⚫ 向下轉型的好處:可以使用子類特有功能。
 ⚫ 弊端是:需要面對具體的子類物件;在向下轉型時容易發生 ClassCastException 型別轉換
異常。在轉換之前必須做型別判斷。
如:if( !a instanceof Dog){…}


總結下封裝、繼承、多型的作用:

⚫ 封裝:把物件的屬性與方法的實現細節隱藏,僅對外提供一些公共的訪問方式
⚫ 繼承:子類會自動擁有父類所有可繼承的屬性和方法。
⚫ 多型:配合繼承與方法重寫提高了程式碼的複用性與擴充套件性;如果沒有方法重寫,則多型同樣沒
有意義