多態性
類似go接口的概念,就是為了讓子類能調用父類的方法
白話:為了實現代碼的通用性
-
一個事物的多種形態
-
父類的引用指向子類的物件(子類的物件賦給父類的引用),舉例:
Father obj = new Child();
-
使用:虛擬方法調用,編譯期只能調用父類中聲明的方法,但運行時執行的是子類重寫父類的方法。多態只有在運行那一個才知道要調用哪個方法,即多態是個運行時行為,又稱為動態綁定
- 白話:編譯看左,運行看右
-
前提:類的繼承關係、方法的重寫,缺一不可
-
目的:避免重複寫很多重載的方法
-
體現:
-
舉例`Person`類 之下有子類 `Chinese`、`Japanese`、`American` `Person`類有`welcome`方法,被各自子類以該國語言重寫過 我的某功能調用`welcome`時能接受`Person`類,依照實際子類物件呈現不同語言的`welcome`結果 該功能即不需要`Chinese`、`Japanese`、`American`都寫一次
-
public class Atest { public static void main(String[] args) { Atest test = new Atest(); test.func(new Dog()); // 多態體現在這 } public void func(Animal animal) { //Animal animal=new Dog(); animal.eat(); animal.shout(); } } class Animal { public void eat() { System.out.println("動物吃"); } public void shout() { System.out.println("動物叫"); } } class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨頭"); } @Override public void shout() { System.out.println("狗旺旺叫"); } }
-
// 調用資料庫 class Driver { public void doData(Connection conn) { // conn=new MySqlConnection conn.method1();
-
只適用於方法,屬性沒有多態!!
- 白話:屬性全看左
小復習
-
// 面試陰險考點 Child objC = new Child() // 造一個子類物件 Father objF=objC; // 子類物件賦值給父類形成多態 此時objF、objC地址值是相同的 // 即objF==objC為true 但他們的屬性可以是各自的(屬性沒有多態) 方法則是動態綁定(編譯看左,運行看右)
-
子類可以獲取父類中
private
的屬性或方法,但無法直接調用 -
方法的重寫:繼承後,同名同參的方法,對於子類重寫的方法來說,權限範圍不能縮小,返回值與拋出異常必須同類或是其子類。被重寫的方法必須非
private
、非static
-
重載:同一類中,只要參數的類型或個數不同,允許存在同名的方法或構造器
-
構造器的繼承與重載
this(形參列表)
:本類重載其他的構造器super(形參列表)
:調用父類中指定的構造器
關鍵字instanceof
前提
父類 多態物件=new 子類
- 當有了物件的多態性以後,因為右邊是有
=new 子類
,記憶體中其實是有加載子類特有的屬性與方法的,只是由於變量聲明為父類類型,導致編譯時無法直接調用 - 可以用"
(子類名)多態物件
“向下強轉,但可能報錯
使用instanceof
a instanceof A
,判斷物件a是否為類A的實例,返回boolean
,舉例:
Animal a1 = new Dog();
if (a1 instanceof Dog) {
Dog a2 = (Dog) a1;
a2.shout();
}
- 對於祖父類也適用,即可以一直向上追溯都能返回
true
的意思 - 實際開發很少用
陰險面試題
class Base { // 一個父類
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub extends Base { // 一個子類
@Override
public void add(int a, int[] arr) {
System.out.println("sub1");
}
public void add(int a, int b, int c) {
System.out.println("sub2");
}
}
public static void main(String[] args) {
Base t = new Sub(); // 創建一個多態的物件t
t.add(1, 2, 3);
// 結果是sub1,因為編譯器認為int...跟int[]一樣
// 形參一樣,構成重寫,所以出來sub1
Sub s = (Sub) t;
s.add(1, 2, 3);
// 子類有自己確定性的方法,優先調用,結果是sub2
int[] arr = new int[]{4, 5, 6};
s.add(1, arr);
// 結果是sub1,理由同上,有優先適用的形參
}
Object類
在Java中Object
類是所有類的父類,意味著Object
類中的屬性與方法具有共通性,以下挑幾個常用的介紹
運算符”=="
- “
==
“可以用在基本數據類型,判斷值是否相等(類型不一定要相同,可能自動提升) - “
==
“也可以用在引用類型,判斷地址是否相等(即是否指向同一個物件)
equals
Object
類中equals()
的定義等同於”==
",比的還是地址值是否相同,源碼如下
public boolean equals(Object obj) {
return (this == obj);
}
- 而String、Date、File、包裝類等都重寫了
Object
類中equals()
方法,重寫後比較的是"實體內容"是否相同 - 通常情況下,我們自訂義類用
equals()
時想比的也是實體內容而非地址,就必須重寫- 重寫的原則:比較類的關鍵屬性是否相同,也可以自動生成
- 對稱性:若
x.equals(y)=true
,則y.equals(x)
也必須是true
- 自反性:
x.equals(x)
=必須true
- 傳遞性:若
x.equals(y)=true
,且y.equals(z)=true
,則z.equals(x)=true
- 一致性:若
x.equals(y)=true
,只要x與y內容不變,則不管重複幾次結果都是true
- 任何情況下
x.equals(null)
必須=flase
,x.equals(非x類對象)
必須=flase
- 對稱性:若
- 重寫的原則:比較類的關鍵屬性是否相同,也可以自動生成
面試題:==與equals()的區別
==
用來比較基本數據類型,而引用類型==
與equals()
就根本上追溯到object類
其實是一樣的,比較的是地址值。只是通常我們在用equals()
時都重寫了,變成用來比較實體內容
toString
-
當我們輸出一個物件的引用時(沒重寫就會是看地址),實際上就是調用
toString()
-
源碼
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- 而String、Date、File、包裝類等都重寫了Object類中
toString()
方法,重寫後返回的是"實體內容”
包裝類
-
把基本數據類型封裝成類,真正實現"一切都是物件”
-
舉例:手動
Float f1 = new Float(12.3)
-
JDK 5.0後,會自動拆裝箱,所以其實直接用就可以了
-
基本數據類型、包裝類都可以簡單地透過
valueOf
轉換成String
,舉例:Float f1 = new Float(12.3); String str = String.valueOf(f1);
-
String
轉回基本數據類型則使用parseXXX
,舉例:String str = "123"; int i = Integer.parseInt(str);
面試題-1
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); // 1.0
乍看可能覺得是`1`,但三元運算符後面兩個條件必須是同類型(否則編譯就報錯了)
於是1被自動類型提升成`Double`,答案變`1.0`。
若把題目那一行拆成if-else結構那就是`1`
面試題-2
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false,地址值不同
Integer m = 1;
Integer n = 1;
System.out.println(m == n); // true
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false,地址值不同
`Integer`內部定義了一個`IntegerCache`結構,造了一個數組保存了 - 128 ~127 內的整數,
我們日常使用自動裝箱其實就是使用這個數組內的元素,不用一直`new`物件,達到增加效率。
而當超出這個範圍,就相當於`new`了新物件,所以x跟y地址不同
練習題-Vector可變長度容器
可以當做一個可變長度的數組來用
添加元素:
vector.addElement(Object obj);
查看元素內容:
vector.elementAt(下標)
public static void main(String[] args) {
Vector v = new Vector();
Scanner scan = new Scanner(System.in);
int maxScore = 0;
boolean loopFlag = true;
for (int i = 0; loopFlag; i++) {
System.out.println("輸入學生成績,負數離開");
int inputInt = scan.nextInt();
if (inputInt < 0) {
loopFlag = false;
break;
}
if (inputInt > 100) {
System.out.println("成績有誤,重新輸入");
continue;
// 這個continue蠻關鍵的,當輸入有誤雖然i++了但不會插入錯誤值到數組中
// 因為"跳過這次"了,下面的語句不會執行
// 再退一步說其實for的i就是多餘的,因為addElement不用遍歷插入值
// loopFlag也是多餘的
}
if (maxScore < inputInt) {
maxScore = inputInt;
}
v.addElement(inputInt);
}
System.out.println("最高分是" + maxScore);
int sum = 0;
for (int i = 0; i < v.size(); i++) {
Object obj = v.elementAt(i);
int score = (int) obj;
if (maxScore - score > 40) {
sum++;
System.out.print("學生" + (i + 1) + "的成績為" + score + ",不合格\n");
} else {
System.out.print("學生" + (i + 1) + "的成績為" + score + ",合格\n");
}
}
System.out.println("不合格人數是" + sum);
上次修改於 2021-11-28