抽象abstract、接口interface、內部類
尚硅谷JavaSE筆記-15

抽象abstract

隨著繼承不斷疊代,子類越來越具體,而父類越來越通用。

類的設計必須保證子類與父類共有特徵,有時候我們將父類設計得非常抽象,以至於它沒有具體的實例,這樣的類稱為抽象類

定義

  • abstract屬於Java中的關鍵字,可以用來修飾類與方法,以下分別說明

abstract修飾類

  • 此類不能實例化
  • 必定有構造器被繼承
  • 開發中都會提供抽象類的子類,讓子類形成實例來調用
  • 不能修飾final類,final類規定不能被繼承,玩毛線

abstract修飾方法

  • 稱為抽象方法,只有聲明,沒有方法體 (就沒要讓你具體用)
  • 此方法不能被調用
  • 包含抽象方法的類,必定是一個抽象類。反之抽象類不一定要有抽象方法
  • 實際開發中的調用必須是被子類繼承後重寫,所有的抽象方法都被重寫後此子類才能實例化(否則,存在繼承來的抽象方法你就是個抽象類)
  • 不能修飾私有private方法,因為抽象就是為了被繼承,抽象與其矛盾
  • 不能修飾靜態static方法,靜態方法跟類共存亡,可以直接被類調用,通常是去弄靜態屬性的,抽象與其矛盾
  • 不能修飾final方法,final方法規定是不能被重寫,抽象與其矛盾

應用-模板方法設計

在軟體開發中,實現某種功能時,整體中很固定、通用的方法,在父類中就寫好了;而其他不確定、易變的就先抽象起來,交給子類去實現

匿名子類的匿名對象

在一次性使用的場合,不想實際造一個匿名類的實體子類,可以在new 匿名類()後面接{}{內直接重寫方法,範例:

abstract public class Person {
    abstract public void work();
}
public class Student extends Person {
    @Override
    public void work() {
        System.out.println("學生讀書");
    }

    public static void main(String[] args) {
        method(new Student());
        method(new Person() {
            @Override
            public void work() {
                System.out.println("用一次的街友");
            }
        });
    }

    public static void method(Person person) {
        person.work();
    }
}

接口interface

繼承是is a的關係,例如學生是人,關係重點在於"是不是";而接口則是"能不能"的關係。Java中,接口跟類是並列的兩個結構(平級關係)

定義

  • 使用關鍵字interface 接口名{}

  • JDK7以前:只能定義全局常量public static final,和抽象方法public abstract

  • JDK8包含以上兩種,還可以定義靜態方法、預設方法

  • 範例:

    interface Flyable {
        public static final int MAX_SPEED = 7900;
        int MIN_SPEED = 1; // 關鍵字可以省略
    
        public abstract void fly();
        void stop(); // 關鍵字可以省略
        }
    
  • 接口中不能定義構造器,它是不能實例化的

  • 接口通過讓類實現(implement)來使用

  • 如果實現類覆蓋(重寫)了接口中的所有方法,則此實現類可以實例化;

  • 反之,沒有完全實現接口的類,就只能是一個抽象類

  • 舉例:

    class Plane implements Flyable{
    
        @Override
        public void fly() {
            System.out.println("飛機起飛");
        }
    
        @Override
        public void stop() {
            System.out.println("飛機停止");
        }
    }
    
  • 一個類可以實現多個接口,(算是JAVA彌補單繼承的一個解決方案),格式範例:

    class A2 extends A1 implement CC,DD,EE
    
  • 接口之間可以繼承,而且可以多繼承,格式範例:

    interface AA extends BB,CC
    

使用

  • 接口實現了多態性
  • 接口其實是定義了一種規範
  • 比如我把某方法的形參用接口,管你啥類只要實現了這個接口,此類實例化的物件就可以調用這個方法,(用起來跟go的沒啥區別)
  • Java8特性:
    • 接口中定義的靜態方法,只能透過接口去調用(類似工具類)
    • 預設方法:使用default修飾,實現類的物件,可以調用接口中的預設方法;若實現時重寫了方法,那調用的還是重寫的方法。只是說用default修飾,可以免去把抽象方法一一實現的過程而直接調用預設的方法
    • 如果實現類繼承的父類和實現的接口聲明了同名同參的方法,沒有重寫的情況下,優先調用父類的那個,稱為類優先原則
    • 如果實現類實現了多個接口,其中存在複數同名同參的方法,在沒有重寫的情況下,報錯-接口衝突
    • 假如有重寫,但想調用接口中的預設方法:接口名.super.方法名

內部類inner class

Java允許在類A中聲明一個類B,此時B是內部類,A稱為外部類。他們在編譯時都會生成字節碼文件(XX.class)

內部類可分成:成員內部類(靜態、非靜態)與局部內部類(方法內、代碼塊內、構造器內),以下細說

成員內部類

  • 作為外部類的成員

    • 可以調用外部類的結構
    • 可以被static修飾
    • 可以被4種權限修飾
  • 另一方面,本身作為一個類

    • 類內可以定義屬性、方法、構造器…等等
    • 內部類可以被繼承(甚至可以被其他類以"extends 外部類.內部類“的方式繼承,但如果內部類沒有static的話,還需要提前建立一個封閉實例,很難搞),可以被abstract修飾
    • 可以被final修飾來表示不能被繼承
  • 舉例:

    public class Animal {
        String name;
    
        void eat() {
            System.out.println("eat something");
        }
    
        static class Cat {
            void show() {
                System.out.println("小貓睡覺");
            }
        }
    
        class Dog {
            void show() {
                System.out.println("小狗看門");
                Animal.this.eat(); // 調用外部類的方法,
                // 其實"Animal.this."可以省略
            }
        }
    }
    
  • 實例化:

    // 創建靜態的成員內部類
    Animal.Cat c1 = new Animal.Cat();
    c1.show();
    
    // 創建非靜態的成員內部類實例,需要先有一個外部成員實例,再用這個實例去new
    Animal a1 = new Animal();
    Animal.Dog d1 = a1.new Dog();
    d1.show();
    
  • 區分調用的屬性或方法:

    用`this.`指向當前內部類,用"外部類名.this."指向外部類
    當然最好還是不要有重名的屬性或方法
    
  • 編譯時生成字節碼文件格式為”外部類$內部類.class"

局部內部類

  • 局部內部類可以聲明在方法內、代碼塊內、構造器內

  • 局部內部類中,若用到其外一層的屬性,則這個屬性必須是final的,舉例:

    public void someMethod() {
       final int age = 18;
        class AA { // 局部內部類
            public void show(){
               age=10; // 會報錯,因為這個age相當於是外面傳進來的副本
                System.out.println(age);
            }
        }
    }
    
  • 實際開發中較少用,比較可能見到是在android開發中,某方法為了實現某個接口,而暫時創建的一個局部內部類。類似上面提到過的匿名子類的匿名對象的用法,舉例:

    // 比如點某按鈕就要跳出某訊息的方法
    public void onCreate() {
        final int number = 10;
        // View.onClickListener()是一個接口需要被實現
        button.setOnClickListener(new View.onClickListener() {
           public void onClick(){
               System.out.println("點按鈕後產生的訊息"+number);
            }
        });
    }
    
  • 編譯時生成字節碼文件格式為"外部類$數字 內部類.class"

小結

  • 抽象類:不能實例化、單繼承、有構造器
  • 接口:不能實例化、多繼承、接口不會有構造器、可以有預設方法

上次修改於 2021-11-30