多態:關鍵字instanceof、Object類與包裝類
尚硅谷JavaSE筆記-13

多態性

類似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; // 子類物件賦值給父類形成多態
    此時objFobjC地址值是相同的 // 即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)必須=flasex.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`了新物件,所以xy地址不同

練習題-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