我們都知道面向對象三大特徵:封裝,繼承,多態
相比於封裝和繼承,多態似乎理解起來更加抽象,這篇文章將徹底揭露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.解釋
在本例中,male
和female
都是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();
這段代碼的時候,因爲man
是person
類型的,所以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的一個實例,返回true
或false
拿第一部的代碼來演示:
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
和狼吞虎嚥
,父類不可以強轉爲子類,如果強轉成功,就失去了父類的特性,多態也就喪失了意義