【Java之軌跡】第三章:面向物件總結(下)
技術標籤:【Java之軌跡】系列筆記java多型
——目錄——
◉ 多型
① 編譯時型別和執行時型別
變數有兩種型別
- 編譯時型別: 變數被宣告出來時所指定的型別,該型別在編譯時起作用,如果不符合某些規範,就算看起來可以執行,也會因為編譯不通過而失敗
- 執行時型別: 變數在編譯時可能是一種型別,但執行時可能又是另一種型別,這另一種型別就稱為執行時型別
② 多型的表現
冰澈理解:多型的表現是由編譯時型別和執行時型別的不同引起的
例子:
public class Main extends Test
{
@Override
public void test()
{
System.out.println("子類的test方法");
}
public static void main(String[] args)
{
Test t1 = new Main();
t1.test();
}
}
public class Test
{
public void test()
{
System.out.println("父類的test方法" );
}
}
先解釋一下Test t1 = new Main();
由於 Test 是 Main 的父類,雖說 Main 繼承了 Test 可能比 Test 多出一些東西來,好像是比 Test 更大
但實際上這只是功能更強而不是範圍更大
Test 可以被很多類繼承,所以範圍相對比於 Main 應該更大
所以這句程式碼的原理是小範圍賦值給大範圍,是符合邏輯的
(相當於發生了自動型別轉換)
就如`long a = 10;`合法一樣,將小範圍的 int 賦給大範圍的 long
那麼問題來了,t1.test() 呼叫的是 Test 的還是 Main 的呢?
我的第一直覺是呼叫 Test 的,因為 t1 的型別正是 Test
![在這裡插入圖片描述](https://img.796t.com/res/2021/02-09/10/952e230d81027fd44329a347b3833740.png)
O.O 呼叫的卻是子類的
理解:
這就和編譯時型別和執行時型別有關係了
編譯時 t1 的型別為 Test ,編譯器會去 Test 尋找 test() 方法
但在執行的時候,由於 Java 是動態繫結的
當執行到Test t1 = new Main();
這一句時,虛擬機器會在堆記憶體中開闢出新的物件 Main 並將 t1 指向 Main 這個物件
到這裡,t1 的型別就已經從 Test 轉化為 Main 了,因為它實質上時指向 Main 這個物件
那麼當執行到t1.test();
這一句時,首先順著地址找到的,肯定就是子類 Main 的 test() 方法了,所以執行結果就是呼叫了子類的。
問題:
那麼既然呼叫的是子類的 test() 方法,可不可以將父類的 test() 方法去掉呢?畢竟沒有用到嘛。
答案肯定是不行的~
去掉後的執行結果:
首先是重寫自檢查發揮作用啦 ~
父類已經沒有這個方法,重寫失敗了
那我們把 @Override 去掉看看什麼樣子:
對了!前面已經說過,在編譯的時候,t1 的型別依舊是 Test,應為還沒有進行動態繫結。
這時候編譯器就會去 Test 中找 test() 方法,結果找不到,也就報錯了
這也說明了 ① 中的看起來可行,但實際上會報錯
結論:
上面這個現象就稱為多型
型別是一樣的,呼叫的方法也一樣,但出來的結果卻是不一樣的,如果每次繫結的子類都不同的話
只要繫結的子類不同,就算型別相同(都是同一個父類)而且呼叫的方法也相同,也會呈現不同的結果,這就是多型!
特別注意: 如果父類和子類有同名的變數,那麼呼叫的變數依舊是父類的!
假設:
Main 中有變數String name = "main";
Test 中也有變數String name = "test";
那麼 t1.name 的結果為 test
原因是: 變數沒有重寫的概念,只是描述物件本身的屬性,而既然 test 的型別為 Test ,那麼該屬性 name 就應該是 Test 的而不是子類 Main的。方法之所以會呼叫子類的,是應為我們主觀上去重寫了該方法,已經明確了要使用子類的!
③ 多型中的強轉問題
前面可以將小範圍的子類賦值給大範圍的父類,那如果反過來呢?
可以看到結果是編譯報錯,就和int a = 12L;
一樣,這是就需要強制轉化了。
問題: 基本資料型別中,大範圍轉小範圍會出現精度損失問題,但是物件之間並不會出現這個問題,那會變成什麼樣子呢?(不會出現物件被砍掉一半吧?!)
試試咯:Main ma = (Main)(new Test());
,結果是…
Exception in thread "main" java.lang.ClassCastException: Test cannot be cast to Main
編譯報錯!直接不讓通過
那如果改成:
Test te = new Main();
Main ma = (Main)te;
那麼結果就正確了~
結論:
多型中可以通過強制轉化將父類轉化為子類,但前提是在發生轉化的時候,該父類的執行時型別必須和子類一致,否則就會出現.ClassCastException
型別轉化異常
實際上如果分開看,強制型別轉化在編譯時並不會報錯,但在執行時發現強轉的前後物件不一致,才會出現錯誤
但在實際操作中我們可能不知道需要強轉的物件是否與目標型別相一致,這時候 instanceof 就可以幫忙了!
④ instanceof 判斷物件型別
對於上面的例子,可能會出現執行執行著突然程式中斷,這樣就太不友好了,我們有義務[doge] 介入處理可能發生的情況
那麼使用 instanceof 改為:
Test te = new Test();
if(te instanceof Main)
{
Main ma = (Main)te;
System.out.println("強轉成功");
}
else
{
System.out.println("強轉失敗,te並不是 Main 型別或者 Main 的子類");
}
執行結果:
這樣就友好多了,不會突然蹦一個錯誤錯來黑掉
程式碼解析:
instanceof 的使用方法: 變數 instanceof 型別
返回值:如果該變數就是該指定型別或者是指定型別的子類,返回true。否則返回false。(返回結果正是進行強制轉化需要滿足的條件~)
注意事項:instanceof 只能用來判斷有繼承關係的物件和型別,如果兩者根本沒有繼承關係在,那麼在編譯階段就會報錯!如:
private static final boolean ICECLEAN = true; (寒冰小澈)