深入淺出java多態性,爲什麼要有多態

我們都知道面向對象三大特徵:封裝,繼承,多態
相比於封裝和繼承,多態似乎理解起來更加抽象,這篇文章將徹底揭露java多態性,文章內容較多,請耐心觀看

1.通過代碼體現多態性

我們先準備好三個類,男人類male,女人類female,以及他們共同的父類person

public class person {
    public void eat(){
        System.out.println("person中的eat方法");
    }
}

public class female extends person {
    @Override
    public void eat() {
        System.out.println("細嚼慢嚥");
    }

    public void walk() {
        System.out.println("female中的的walk");
    }
}


public class male extends person {
    @Override
    public void eat() {
        System.out.println("狼吞虎嚥");
    }

    public void walk() {
        System.out.println("male中的的walk");
    }
}

先通過最熟悉的創建對象的方式來測試這些類

public class test {
    public static void main(String[] args) {
        person p = new person();
        male man = new male();
        female woman = new female();
        p.eat();
        man.eat();
        woman.eat();
    }
}

輸出結果:

person中的eat方法
狼吞虎嚥
細嚼慢嚥

這些都是非常正常的,毫無疑問,我們改一下test中的代碼

public class test {
    public static void main(String[] args) {
        person p = new person();
        person man = new male();
        person woman = new female();
        p.eat();
        man.eat();
        woman.eat();
    }
}

請看輸出結果:

person中的eat方法
狼吞虎嚥
細嚼慢嚥

通過上面的代碼,可以提出幾個疑問:

  • person man = new male();爲什麼沒有報錯?
  • person man = new male();中,man屬於person類聲明的對象引用,爲什麼不調用person中的方法

2.解釋

在本例中,malefemale都是person的子類,我們做的測試都是基於這個條件,這就引出了多態成立的一個條件:

  • 類和類存在繼承關係,父類引用指向子類對象,子類重寫父類的方法

通過代碼我們可以發現,通過person聲明的引用可以是male,也可以是female,這就體現了多態性,即一個引用可以有不同的實現方式,說簡單就是,只要是person的子類,都可以賦給person聲明的引用

  • 需要注意的是:person man = new male();,中,雖然man是person的引用,但實際指向的是male在堆上創建的male對象,所以man調用的方法就是male中重寫父類的方法了,這就涉及到了一個概念:虛擬方法調用,文章下面會講

不要忘了我們male類中還有一個方法,這不是白寫的,我們在person man = new male();前提下來調用這個方法:
在這裏插入圖片描述
發現報錯了,無法通過編譯!,爲什麼會這樣,請接着往下看:

3.多態條件下的同名同參的方法調用

在編譯期,也就是將我們寫好的java代碼編譯成class文件的時候,在編譯器編譯
person man = new male();這段代碼的時候,因爲manperson類型的,所以man在本質上一個person類型的引用,這是我們寫代碼的時候聲明的,毫無疑問,所以所以man只能調用在person類中出現過的方法,請記住下面三句話:

  • 如果子類重寫了這個方法,且這兩個方法同名同參,就調用子類的
  • 如果子類沒有這個方法,就調用父類的,
  • 不能調用子類中存在而父類中不存在的方法!
  • 編譯器看左邊,運行期執行的是子類重寫父類的方法

4.爲什麼要有多態

在我們編寫代碼的時候,儘量本着高內聚,低耦合的原則去編寫,不理解這個概念沒關係,我們通過一個案例來表示

public class test {
    public static void main(String[] args) {
        fun(new Dog());
        fun(new Cat());
    }

    public static void fun(Animal animal){
        animal.action();
    }
}
class Animal{
    public void action(){}
}
class Dog extends Animal{
    @Override
    public void action() {
        System.out.println("狗類的動作");
    }
}
class Cat extends Animal{
    @Override
    public void action() {
        System.out.println("貓類的動作");
    }
}

test類中定義的fun方法中,參數並沒有寫死,而是只要是Animal的子類都可以調用,這樣就避免課將參數寫死而導致代碼的難以服用,在以後的框架以及其他技術中,這類的寫法非常之多,如果沒有多態,那麼抽象類和接口也就沒有意義,體現不了java的面向對象編程思想

5.關於屬性的多態

我們來一個騷操作

public class test {
    public static void main(String[] args) {
        Animal a = new Dog();
        System.out.println(a.id);
    }


}
class Animal{
    int id = 1;
}
class Dog extends Animal{
   int id = 2;
}

輸出:1
到這裏就看不懂了,a是animal類型的,調用的是animal中的屬性,不做多解釋,我們拋出一個結論:

  • 多態只適用於方法,不適合屬性

6.虛擬方法調用

在這裏插入圖片描述

7.向下轉型

有了對象的多態性之後,內存中其實是加載了子類的屬性和方法的,但由於聲明的引用是父類類型,導致編譯時只能調用父類中的屬性和方法,子類特有的屬性哥和方法無法調用,仔細看這句話,如何才能調用子類特有的屬性和方法?
解決辦法:強轉
用我們第一步的代碼來看:

public class test {
    public static void main(String[] args) {
       person p = new male();
       male m =(male)p;
    }
}

這和基本數據類型的強轉差不多,這樣就可以使用子類特有的屬性和方法了,這就是向下轉型
類比基本數據類型,可以看一下這張圖:
在這裏插入圖片描述
是不是容易理解多了,這裏多提一下向上轉型,說白了向上轉型就是多態,我們之前的案例都是向上轉型,即父類引用指向子類對象
在進行類型轉換的時候可能會出現很多問題,在這裏有必要說一下:

  • 問題一:編譯時通過,運行時報錯
    看代碼:用第一步的代碼來做演示:
    person p = new male();
    female f = (female)p;
    f.walk();
    
    在運行的時候會拋出:Exception in thread "main" java.lang.ClassCastException類型轉換錯誤異常
  • 問題二:編譯時通過,運行時通過
     	 Object o = new male();
         person p1 = (person)o;
         p1.eat();
    

8.instanceof

在做類型轉換的時候要使用instanceof

  • a instanceof A:判斷a是不是A的一個實例,返回truefalse

拿第一部的代碼來演示:

public class test {
    public static void main(String[] args) {
        person p = new male();
        System.out.println(p instanceof male);
        female f = new female();
        System.out.println(f instanceof person);
        
    }
}

輸出結果:

true
true

9.關於向下轉型的碎碎念

還是第一步的代碼,對比兩段代碼

		person p2 = new person();
        if(p2 instanceof male){
            male m1 = (male)p2;
        }else{
            System.out.println("wrong");
        }


		person p3 = new male();
        if(p3 instanceof male){
            male m2 = (male)p3;
            m2.eat();
        }else{
            System.out.println("wrong");
        }

結果輸出的時wrong狼吞虎嚥,父類不可以強轉爲子類,如果強轉成功,就失去了父類的特性,多態也就喪失了意義

10.總結

在這裏插入圖片描述

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