1.1 多型的形式
多型是繼封裝、繼承之後,物件導向的第三大特性。
多型是出現在繼承或者實現關係中的。
多型體現的格式:
父類型別 變數名 = new 子類/實現類構造器;
變數名.方法名();
多型的前提:有繼承關係,子類物件是可以賦值給父類型別的變數。例如Animal是一個動物型別,而Cat是一個貓型別。Cat繼承了Animal,Cat物件也是Animal型別,自然可以賦值給父類型別的變數。
1.2 多型的使用場景
如果沒有多型,在下圖中register方法只能傳遞學生物件,其他的Teacher和administrator物件是無法傳遞給register方法方法的,在這種情況下,只能定義三個不同的register方法分別接收學生,老師和管理員。
有了多型之後,方法的形參就可以定義為共同的父類Person。
要注意的是:
- 當一個方法的形參是一個類,我們可以傳遞這個類所有的子類物件。
- 當一個方法的形參是一個介面,我們可以傳遞這個介面所有的實現類物件(後面會學)。
- 而且多型還可以根據傳遞的不同物件來呼叫不同類中的方法。
程式碼示例:
父類:
public class Person {
private String name;
private int age;
空參構造
帶全部引數的構造
get和set方法
public void show(){
System.out.println(name + ", " + age);
}
}
子類1:
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理員的資訊為:" + getName() + ", " + getAge());
}
}
子類2:
public class Student extends Person{
@Override
public void show() {
System.out.println("學生的資訊為:" + getName() + ", " + getAge());
}
}
子類3:
public class Teacher extends Person{
@Override
public void show() {
System.out.println("老師的資訊為:" + getName() + ", " + getAge());
}
}
測試類:
public class Test {
public static void main(String[] args) {
//建立三個物件,並呼叫register方法
Student s = new Student();
s.setName("張三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("王建國");
t.setAge(30);
Administrator admin = new Administrator();
admin.setName("管理員");
admin.setAge(35);
register(s);
register(t);
register(admin);
}
//這個方法既能接收老師,又能接收學生,還能接收管理員
//只能把引數寫成這三個型別的父類
public static void register(Person p){
p.show();
}
}
1.3 多型的定義和前提
多型: 是指同一行為,具有多個不同表現形式。
從上面案例可以看出,Cat和Dog都是動物,都是吃這一行為,但是出現的效果(表現形式)是不一樣的。
前提【重點】
- 有繼承或者實現關係
- 方法的重寫【意義體現:不重寫,無意義】
父類引用指向子類物件【格式體現】
父類型別:指子類物件繼承的父類型別,或者實現的父介面型別。
1.4 多型的執行特點
呼叫成員變數時:編譯看左邊,執行看左邊
呼叫成員方法時:編譯看左邊,執行看右邊
程式碼示例:
Fu f = new Zi();
//編譯看左邊的父類中有沒有name這個屬性,沒有就報錯
//在實際執行的時候,把父類name屬性的值列印出來
System.out.println(f.name);
//編譯看左邊的父類中有沒有show這個方法,沒有就報錯
//在實際執行的時候,執行的是子類中的show方法
f.show();
1.5 多型的弊端
我們已經知道多型編譯階段是看左邊父類型別的,如果子類有些獨有的功能,此時多型的寫法就無法訪問子類獨有功能了。
class Animal{
public void eat(){
System.out.println("動物吃東西!")
}
}
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
}
class Test{
public static void main(String[] args){
Animal a = new Cat();
a.eat();
a.catchMouse();//編譯報錯,編譯看左邊,Animal沒有這個方法
}
}
1.6 引用型別轉換
1.6.1 為什麼要轉型
多型的寫法就無法訪問子類獨有功能了。
當使用多型方式呼叫方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能呼叫子類擁有,而父類沒有的方法。編譯都錯誤,更別說執行了。這也是多型給我們帶來的一點"小麻煩"。所以,想要呼叫子類特有的方法,必須做向下轉型。
回顧基本資料型別轉換
- 自動轉換: 範圍小的賦值給範圍大的.自動完成:double d = 5;
- 強制轉換: 範圍大的賦值給範圍小的,強制轉換:int i = (int)3.14
多型的轉型分為向上轉型(自動轉換)與向下轉型(強制轉換)兩種。
1.6.2 向上轉型(自動轉換)
- 向上轉型:多型本身是子類型別向父類型別向上轉換(自動轉換)的過程,這個過程是預設的。
當父類引用指向一個子類物件時,便是向上轉型。
使用格式:
父類型別 變數名 = new 子類型別();
如:Animal a = new Cat();
原因是:父類型別相對與子類來說是大範圍的型別,Animal是動物類,是父類型別。Cat是貓類,是子類型別。Animal型別的範圍當然很大,包含一切動物。所以子類範圍小可以直接自動轉型給父類型別的變數。
1.6.3 向下轉型(強制轉換)
- 向下轉型:父類型別向子類型別向下轉換的過程,這個過程是強制的。
一個已經向上轉型的子類物件,將父類引用轉為子類引用,可以使用強制型別轉換的格式,便是向下轉型。
使用格式:
子類型別 變數名 = (子類型別) 父類變數名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
1.6.4 案例演示
當使用多型方式呼叫方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能呼叫子類擁有,而父類沒有的方法。編譯都錯誤,更別說執行了。這也是多型給我們帶來的一點"小麻煩"。所以,想要呼叫子類特有的方法,必須做向下轉型。
轉型演示,程式碼如下:
定義類:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
public void watchHouse() {
System.out.println("看家");
}
}
定義測試類:
public class Test {
public static void main(String[] args) {
// 向上轉型
Animal a = new Cat();
a.eat(); // 呼叫的是 Cat 的 eat
// 向下轉型
Cat c = (Cat)a;
c.catchMouse(); // 呼叫的是 Cat 的 catchMouse
}
}
1.6.5 轉型的異常
轉型的過程中,一不小心就會遇到這樣的問題,請看如下程式碼:
public class Test {
public static void main(String[] args) {
// 向上轉型
Animal a = new Cat();
a.eat(); // 呼叫的是 Cat 的 eat
// 向下轉型
Dog d = (Dog)a;
d.watchHouse(); // 呼叫的是 Dog 的 watchHouse 【執行報錯】
}
}
這段程式碼可以透過編譯,但是執行時,卻報出了 ClassCastException
,型別轉換異常!這是因為,明明建立了Cat型別物件,執行時,當然不能轉換成Dog物件的。
1.6.6 instanceof關鍵字
為了避免ClassCastException的發生,Java提供了 instanceof
關鍵字,給引用變數做型別的校驗,格式如下:
變數名 instanceof 資料型別
如果變數屬於該資料型別或者其子類型別,返回true。
如果變數不屬於該資料型別或者其子類型別,返回false。
所以,轉換前,我們最好先做一個判斷,程式碼如下:
public class Test {
public static void main(String[] args) {
// 向上轉型
Animal a = new Cat();
a.eat(); // 呼叫的是 Cat 的 eat
// 向下轉型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 呼叫的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 呼叫的是 Dog 的 watchHouse
}
}
}
1.6.7 instanceof新特性
JDK14的時候提出了新特性,把判斷和強轉合併成了一行
//新特性
//先判斷a是否為Dog型別,如果是,則強轉成Dog型別,轉換之後變數名為d
//如果不是,則不強轉,結果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("沒有這個型別,無法轉換");
}
1.7 綜合練習
需求:根據需求完成程式碼:
1.定義狗類
屬性:
年齡,顏色
行為:
eat(String something)(something表示吃的東西)
看家lookHome方法(無引數)
2.定義貓類
屬性:
年齡,顏色
行為:
eat(String something)方法(something表示吃的東西)
逮老鼠catchMouse方法(無引數)
3.定義Person類//飼養員
屬性:
姓名,年齡
行為:
keepPet(Dog dog,String something)方法
功能:餵養寵物狗,something表示餵養的東西
行為:
keepPet(Cat cat,String something)方法
功能:餵養寵物貓,something表示餵養的東西
生成空參有參構造,set和get方法
4.定義測試類(完成以下列印效果):
keepPet(Dog dog,String somethind)方法列印內容如下:
年齡為30歲的老王養了一隻黑顏色的2歲的狗
2歲的黑顏色的狗兩隻前腿死死的抱住骨頭猛吃
keepPet(Cat cat,String somethind)方法列印內容如下:
年齡為25歲的老李養了一隻灰顏色的3歲的貓
3歲的灰顏色的貓眯著眼睛側著頭吃魚
5.思考:
1.Dog和Cat都是Animal的子類,以上案例中針對不同的動物,定義了不同的keepPet方法,過於繁瑣,能否簡化,並體會簡化後的好處?
2.Dog和Cat雖然都是Animal的子類,但是都有其特有方法,能否想辦法在keepPet中呼叫特有方法?
畫圖分析:
程式碼示例:
//動物類(父類)
public class Animal {
private int age;
private String color;
public Animal() {
}
public Animal(int age, String color) {
this.age = age;
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void eat(String something){
System.out.println("動物在吃" + something);
}
}
//貓類(子類)
public class Cat extends Animal {
public Cat() {
}
public Cat(int age, String color) {
super(age, color);
}
@Override
public void eat(String something) {
System.out.println(getAge() + "歲的" + getColor() + "顏色的貓眯著眼睛側著頭吃" + something);
}
public void catchMouse(){
System.out.println("貓抓老鼠");
}
}
//狗類(子類)
public class Dog extends Animal {
public Dog() {
}
public Dog(int age, String color) {
super(age, color);
}
//行為
//eat(String something)(something表示吃的東西)
//看家lookHome方法(無引數)
@Override
public void eat(String something) {
System.out.println(getAge() + "歲的" + getColor() + "顏色的狗兩隻前腿死死的抱住" + something + "猛吃");
}
public void lookHome(){
System.out.println("狗在看家");
}
}
//飼養員類
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//飼養狗
/* public void keepPet(Dog dog, String something) {
System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + dog.getColor() + "顏色的" + dog.getAge() + "歲的狗");
dog.eat(something);
}
//飼養貓
public void keepPet(Cat cat, String something) {
System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + cat.getColor() + "顏色的" + cat.getAge() + "歲的貓");
cat.eat(something);
}*/
//想要一個方法,能接收所有的動物,包括貓,包括狗
//方法的形參:可以寫這些類的父類 Animal
public void keepPet(Animal a, String something) {
if(a instanceof Dog d){
System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + a.getColor() + "顏色的" + a.getAge() + "歲的狗");
d.eat(something);
}else if(a instanceof Cat c){
System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + c.getColor() + "顏色的" + c.getAge() + "歲的貓");
c.eat(something);
}else{
System.out.println("沒有這種動物");
}
}
}
//測試類
public class Test {
public static void main(String[] args) {
//建立物件並呼叫方法
/* Person p1 = new Person("老王",30);
Dog d = new Dog(2,"黑");
p1.keepPet(d,"骨頭");
Person p2 = new Person("老李",25);
Cat c = new Cat(3,"灰");
p2.keepPet(c,"魚");*/
//建立飼養員的物件
Person p = new Person("老王",30);
Dog d = new Dog(2,"黑");
Cat c = new Cat(3,"灰");
p.keepPet(d,"骨頭");
p.keepPet(c,"魚");
}
}