Java 的抽象類, 介面以及內部類

瑪拉_以琳發表於2023-03-02

第一章 抽象類

1.1 概述

1.1.1 抽象類引入

​ 父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類的方法宣告和方法主體,只有宣告還有意義,而方法主體則沒有存在的意義了(因為子類物件會呼叫自己重寫的方法)。換句話說,父類可能知道子類應該有哪個功能,但是功能具體怎麼實現父類是不清楚的(由子類自己決定),父類只需要提供一個沒有方法體的定義即可,具體實現交給子類自己去實現。我們把沒有方法體的方法稱為抽象方法。Java語法規定,包含抽象方法的類就是抽象類

  • 抽象方法 : 沒有方法體的方法。
  • 抽象類:包含抽象方法的類。

1.2 abstract使用格式

abstract是抽象的意思,用於修飾方法方法和類,修飾的方法是抽象方法,修飾的類是抽象類。

1.2.1 抽象方法

使用abstract 關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。

定義格式:

修飾符 abstract 返回值型別 方法名 (引數列表);

程式碼舉例:

public abstract void run();

1.2.2 抽象類

如果一個類包含抽象方法,那麼該類必須是抽象類。注意:抽象類不一定有抽象方法,但是有抽象方法的類必須定義成抽象類。

定義格式:

abstract class 類名字 { 
  
}

程式碼舉例:

public abstract class Animal {
    public abstract void run();
}

1.2.3 抽象類的使用

要求:繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須宣告為抽象類。

程式碼舉例:

// 父類,抽象類
abstract class Employee {
    private String id;
    private String name;
    private double salary;
    
    public Employee() {
    }
    
    public Employee(String id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
    
    // 抽象方法
    // 抽象方法必須要放在抽象類中
    abstract public void work();
}

// 定義一個子類繼承抽象類
class Manager extends Employee {
    public Manager() {
    }
    public Manager(String id, String name, double salary) {
        super(id, name, salary);
    }
    // 2.重寫父類的抽象方法
    @Override
    public void work() {
        System.out.println("管理其他人");
    }
}

// 定義一個子類繼承抽象類
class Cook extends Employee {
    public Cook() {
    }
    public Cook(String id, String name, double salary) {
        super(id, name, salary);
    }
    @Override
    public void work() {
        System.out.println("廚師炒菜多加點鹽...");
    }
}

// 測試類
public class Demo10 {
    public static void main(String[] args) {
        // 建立抽象類,抽象類不能建立物件
        // 假設抽象類讓我們建立物件,裡面的抽象方法沒有方法體,無法執行.所以不讓我們建立物件
//        Employee e = new Employee();
//        e.work();
        
        // 3.建立子類
        Manager m = new Manager();
        m.work();
        
        Cook c = new Cook("ap002", "庫克", 1);
        c.work();
    }
}

此時的方法重寫,是子類對父類抽象方法的完成實現,我們將這種方法重寫的操作,也叫做實現方法

1.3 抽象類的特徵

抽象類的特徵總結起來可以說是 有得有失

有得:抽象類得到了擁有抽象方法的能力。

有失:抽象類失去了建立物件的能力。

其他成員(構造方法,例項方法,靜態方法等)抽象類都是具備的。

1.4 抽象類的細節

不需要背,只要當idea報錯之後,知道如何修改即可。

關於抽象類的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。

  1. 抽象類不能建立物件,如果建立,編譯無法透過而報錯。只能建立其非抽象子類的物件。

    理解:假設建立了抽象類的物件,呼叫抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
  2. 抽象類中,可以有構造方法,是供子類建立物件時,初始化父類成員使用的。

    理解:子類的構造方法中,有預設的super(),需要訪問父類構造方法。
  3. 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。

    理解:未包含抽象方法的抽象類,目的就是不想讓呼叫者建立該類物件,通常用於某些特殊的類結構設計。
  4. 抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則子類也必須定義成抽象類,編譯無法透過而報錯。

    理解:假設不重寫所有抽象方法,則類中可能包含抽象方法。那麼建立物件後,呼叫抽象的方法,沒有意義。
  5. 抽象類存在的意義是為了被子類繼承。

    理解:抽象類中已經實現的是模板中確定的成員,抽象類不確定如何實現的定義成抽象方法,交給具體的子類去實現。

1.5 抽象類存在的意義

​ 抽象類存在的意義是為了被子類繼承,否則抽象類將毫無意義。抽象類可以強制讓子類,一定要按照規定的格式進行重寫。

第二章 介面

2.1 概述

我們已經學完了抽象類,抽象類中可以用抽象方法,也可以有普通方法,構造方法,成員變數等。那麼什麼是介面呢?介面是更加徹底的抽象,JDK7之前,包括JDK7,介面中全部是抽象方法。介面同樣是不能建立物件的

2.2 定義格式

//介面的定義格式:
interface 介面名稱{
    // 抽象方法
}

// 介面的宣告:interface
// 介面名稱:首字母大寫,滿足“駝峰模式”

2.3 介面成分的特點

在JDK7,包括JDK7之前,介面中的只有包含:抽象方法和常量

2.3.1.抽象方法

​ 注意:介面中的抽象方法預設會自動加上public abstract修飾程式設計師無需自己手寫!!
​ 按照規範:以後介面中的抽象方法建議不要寫上public abstract。因為沒有必要啊,預設會加上。

2.3.2 常量

在介面中定義的成員變數預設會加上: public static final修飾。也就是說在介面中定義的成員變數實際上是一個常量。這裡是使用public static final修飾後,變數值就不可被修改,並且是靜態化的變數可以直接用介面名訪問,所以也叫常量。常量必須要給初始值。常量命名規範建議字母全部大寫,多個單詞用下劃線連線。

2.3.3 案例演示

public interface InterF {
    // 抽象方法!
    //    public abstract void run();
    void run();

    //    public abstract String getName();
    String getName();

    //    public abstract int add(int a , int b);
    int add(int a , int b);


    // 它的最終寫法是:
    // public static final int AGE = 12 ;
    int AGE  = 12; //常量
    String SCHOOL_NAME = "黑馬程式設計師";

}

2.4 基本的實現

2.4.1 實現介面的概述

類與介面的關係為實現關係,即類實現介面,該類可以稱為介面的實現類,也可以稱為介面的子類。實現的動作類似繼承,格式相仿,只是關鍵字不同,實現使用 implements關鍵字。

2.4.2 實現介面的格式

/**介面的實現:
    在Java中介面是被實現的,實現介面的類稱為實現類。
    實現類的格式:*/
class 類名 implements 介面1,介面2,介面3...{

}

從上面格式可以看出,介面是可以被多實現的。大家可以想一想為什麼呢?

2.4.3 類實現介面的要求和意義

  1. 必須重寫實現的全部介面中所有抽象方法。
  2. 如果一個類實現了介面,但是沒有重寫完全部介面的全部抽象方法,這個類也必須定義成抽象類。
  3. 意義:介面體現的是一種規範,介面對實現類是一種強制性的約束,要麼全部完成介面申明的功能,要麼自己也定義成抽象類。這正是一種強制性的規範。

2.4.4 類與介面基本實現案例

假如我們定義一個運動員的介面(規範),程式碼如下:

/**
   介面:介面體現的是規範。
 * */
public interface SportMan {
    void run(); // 抽象方法,跑步。
    void law(); // 抽象方法,遵守法律。
    String compittion(String project);  // 抽象方法,比賽。
}

接下來定義一個乒乓球運動員類,實現介面,實現介面的實現類程式碼如下:

package com.itheima._03介面的實現;
/**
 * 介面的實現:
 *    在Java中介面是被實現的,實現介面的類稱為實現類。
 *    實現類的格式:
 *      class 類名 implements 介面1,介面2,介面3...{
 *
 *
 *      }
 * */
public class PingPongMan  implements SportMan {
    @Override
    public void run() {
        System.out.println("乒乓球運動員稍微跑一下!!");
    }

    @Override
    public void law() {
        System.out.println("乒乓球運動員守法!");
    }

    @Override
    public String compittion(String project) {
        return "參加"+project+"得金牌!";
    }
}

測試程式碼

public class TestMain {
    public static void main(String[] args) {
        // 建立實現類物件。
        PingPongMan zjk = new PingPongMan();
        zjk.run();
        zjk.law();
        System.out.println(zjk.compittion("全球乒乓球比賽"));

    }
}

2.4.5 類與介面的多實現案例

類與介面之間的關係是多實現的,一個類可以同時實現多個介面。

首先我們先定義兩個介面,程式碼如下:

/** 法律規範:介面*/
public interface Law {
    void rule();
}

/** 這一個運動員的規範:介面*/
public interface SportMan {
    void run();
}

然後定義一個實現類:

/**
 * Java中介面是可以被多實現的:
 *    一個類可以實現多個介面: Law, SportMan
 *
 * */
public class JumpMan implements Law ,SportMan {
    @Override
    public void rule() {
        System.out.println("尊長守法");
    }

    @Override
    public void run() {
        System.out.println("訓練跑步!");
    }
}

從上面可以看出類與介面之間是可以多實現的,我們可以理解成實現多個規範,這是合理的。

2.5 介面與介面的多繼承

Java中,介面與介面之間是可以多繼承的:也就是一個介面可以同時繼承多個介面。大家一定要注意:

類與介面是實現關係

介面與介面是繼承關係

介面繼承介面就是把其他介面的抽象方法與本介面進行了合併。

案例演示:

public interface Abc {
    void go();
    void test();
}

/** 法律規範:介面*/
public interface Law {
    void rule();
    void test();
}

 *
 *  總結:
 *     介面與類之間是多實現的。
 *     介面與介面之間是多繼承的。
 * */
public interface SportMan extends Law , Abc {
    void run();
}

2.6擴充套件:介面的細節

不需要背,只要當idea報錯之後,知道如何修改即可。

關於介面的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。

  1. 當兩個介面中存在相同抽象方法的時候,該怎麼辦?
只要重寫一次即可。此時重寫的方法,既表示重寫1介面的,也表示重寫2介面的。
  1. 實現類能不能繼承A類的時候,同時實現其他介面呢?
繼承的父類,就好比是親爸爸一樣
實現的介面,就好比是乾爹一樣
可以繼承一個類的同時,再實現多個介面,只不過,要把介面裡面所有的抽象方法,全部實現。
  1. 實現類能不能繼承一個抽象類的時候,同時實現其他介面呢?
實現類可以繼承一個抽象類的同時,再實現其他多個介面,只不過要把裡面所有的抽象方法全部重寫。
  1. 實現類Zi,實現了一個介面,還繼承了一個Fu類。假設在介面中有一個方法,父類中也有一個相同的方法。子類如何操作呢?
處理辦法一:如果父類中的方法體,能滿足當前業務的需求,在子類中可以不用重寫。
處理辦法二:如果父類中的方法體,不能滿足當前業務的需求,需要在子類中重寫。
  1. 如果一個介面中,有10個抽象方法,但是我在實現類中,只需要用其中一個,該怎麼辦?
可以在介面跟實現類中間,新建一箇中間類(介面卡類)
讓這個介面卡類去實現介面,對介面裡面的所有的方法做空重寫。
讓子類繼承這個介面卡類,想要用到哪個方法,就重寫哪個方法。
因為中間類沒有什麼實際的意義,所以一般會把中間類定義為抽象的,不讓外界建立物件

第三章 內部類

3.1 概述

3.1.1 什麼是內部類

將一個類A定義在另一個類B裡面,裡面的那個類A就稱為內部類,B則稱為外部類。可以把內部類理解成寄生,外部類理解成宿主。

3.1.2 什麼時候使用內部類

一個事物內部還有一個獨立的事物,內部的事物脫離外部的事物無法獨立使用

  1. 人裡面有一顆心臟。
  2. 汽車內部有一個發動機。
  3. 為了實現更好的封裝性。

3.2 內部類的分類

按定義的位置來分

  1. 成員內部內,類定義在了成員位置 (類中方法外稱為成員位置,無static修飾的內部類)
  2. 靜態內部類,類定義在了成員位置 (類中方法外稱為成員位置,有static修飾的內部類)
  3. 區域性內部類,類定義在方法內
  4. 匿名內部類,沒有名字的內部類,可以在方法中,也可以在類中方法外。

3.3 成員內部類

成員內部類特點

  • 無static修飾的內部類,屬於外部類物件的。
  • 宿主:外部類物件。

內部類的使用格式

 外部類.內部類。 // 訪問內部類的型別都是用 外部類.內部類

獲取成員內部類物件的兩種方式

方式一:外部直接建立成員內部類的物件

外部類.內部類 變數 = new 外部類().new 內部類();

方式二:在外部類中定義一個方法提供內部類的物件

案例演示

方式一:
public class Test {
    public static void main(String[] args) {
        //  宿主:外部類物件。
       // Outer out = new Outer();
        // 建立內部類物件。
        Outer.Inner oi = new Outer().new Inner();
        oi.method();
    }
}

class Outer {
    // 成員內部類,屬於外部類物件的。
    // 擴充:成員內部類不能定義靜態成員。
    public class Inner{
        // 這裡面的東西與類是完全一樣的。
        public void method(){
            System.out.println("內部類中的方法被呼叫了");
        }
    }
}


方式二:
public class Outer {
    String name;
    private class Inner{
        static int a = 10;
    }
    public Inner getInstance(){
        return new Inner();
    }
}

public class Test {
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.getInstance());


    }
}

3.4 成員內部類的細節

編寫成員內部類的注意點:

  1. 成員內部類可以被一些修飾符所修飾,比如: private,預設,protected,public,static等
  2. 在成員內部類裡面,JDK16之前不能定義靜態變數,JDK16開始才可以定義靜態變數。
  3. 建立內部類物件時,物件中有一個隱含的Outer.this記錄外部類物件的地址值。(請參見3.6節的記憶體圖)

詳解:

​ 內部類被private修飾,外界無法直接獲取內部類的物件,只能透過3.3節中的方式二獲取內部類的物件

​ 被其他許可權修飾符修飾的內部類一般用3.3節中的方式一直接獲取內部類的物件

​ 內部類被static修飾是成員內部類中的特殊情況,叫做靜態內部類下面單獨學習。

​ 內部類如果想要訪問外部類的成員變數,外部類的變數必須用final修飾,JDK8以前必須手動寫final,JDK8之後不需要手動寫,JDK預設加上。

3.5 成員內部類面試題

請在?地方向上相應程式碼,以達到輸出的內容

注意:內部類訪問外部類物件的格式是:外部類名.this

public class Test {
    public static void main(String[] args) {
        Outer.inner oi = new Outer().new inner();
        oi.method();
    }
}

class Outer {    // 外部類
    private int a = 30;

    // 在成員位置定義一個類
    class inner {
        private int a = 20;

        public void method() {
            int a = 10;
            System.out.println(???);    // 10   答案:a
            System.out.println(???);    // 20    答案:this.a
            System.out.println(???);    // 30    答案:Outer.this.a
        }
    }
}

3.6 成員內部類記憶體圖

內部類記憶體圖.png

3.7 靜態內部類

靜態內部類特點

  • 靜態內部類是一種特殊的成員內部類。
  • 有static修飾,屬於外部類本身的。
  • 總結:靜態內部類與其他類的用法完全一樣。只是訪問的時候需要加上外部類.內部類。
  • 擴充1:靜態內部類可以直接訪問外部類的靜態成員。
  • 擴充2:靜態內部類不可以直接訪問外部類的非靜態成員,如果要訪問需要建立外部類的物件。
  • 擴充3:靜態內部類中沒有銀行的Outer.this。

內部類的使用格式

外部類.內部類。

靜態內部類物件的建立格式

外部類.內部類  變數 = new  外部類.內部類構造器;

呼叫方法的格式:

  • 呼叫非靜態方法的格式:先建立物件,用物件呼叫
  • 呼叫靜態方法的格式:外部類名.內部類名.方法名();

案例演示

// 外部類:Outer01
class Outer01{
    private static  String sc_name = "黑馬程式";
    // 內部類: Inner01
    public static class Inner01{
        // 這裡面的東西與類是完全一樣的。
        private String name;
        public Inner01(String name) {
            this.name = name;
        }
        public void showName(){
            System.out.println(this.name);
            // 擴充:靜態內部類可以直接訪問外部類的靜態成員。
            System.out.println(sc_name);
        }
    }
}

public class InnerClassDemo01 {
    public static void main(String[] args) {
        // 建立靜態內部類物件。
        // 外部類.內部類  變數 = new  外部類.內部類構造器;
        Outer01.Inner01 in  = new Outer01.Inner01("張三");
        in.showName();
    }
}

3.8 區域性內部類

  • 區域性內部類 :定義在方法中的類。

定義格式:

class 外部類名 {
    資料型別 變數名;
    
    修飾符 返回值型別 方法名(引數列表) {
        // …
        class 內部類 {
            // 成員變數
            // 成員方法
        }
    }
}

3.9 匿名內部類【重點】

3.9.1 概述

匿名內部類 :是內部類的簡化寫法。他是一個隱含了名字的內部類。開發中,最常用到的內部類就是匿名內部類了。

3.9.2 格式

new 類名或者介面名() {
     重寫方法;
};

包含了:

  • 繼承或者實現關係
  • 方法重寫
  • 建立物件

所以從語法上來講,這個整體其實是匿名內部類物件

3.9.2 什麼時候用到匿名內部類

實際上,如果我們希望定義一個只要使用一次的類,就可考慮使用匿名內部類。匿名內部類的本質作用

是為了簡化程式碼

之前我們使用介面時,似乎得做如下幾步操作:

  1. 定義子類
  2. 重寫介面中的方法
  3. 建立子類物件
  4. 呼叫重寫後的方法
interface Swim {
    public abstract void swimming();
}

// 1. 定義介面的實現類
class Student implements Swim {
    // 2. 重寫抽象方法
    @Override
    public void swimming() {
        System.out.println("狗刨式...");
    }
}

public class Test {
    public static void main(String[] args) {
        // 3. 建立實現類物件
        Student s = new Student();
        // 4. 呼叫方法
        s.swimming();
    }
}

我們的目的,最終只是為了呼叫方法,那麼能不能簡化一下,把以上四步合成一步呢?匿名內部類就是做這樣的快捷方式。

3.9.3 匿名內部類前提和格式

匿名內部類必須繼承一個父類或者實現一個父介面

匿名內部類格式

new 父類名或者介面名(){
    // 方法重寫
    @Override 
    public void method() {
        // 執行語句
    }
};

3.9.4 使用方式

以介面為例,匿名內部類的使用,程式碼如下:

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 使用匿名內部類
        new Swim() {
            @Override
            public void swimming() {
                System.out.println("自由泳...");
            }
        }.swimming();

        // 介面 變數 = new 實現類(); // 多型,走子類的重寫方法
        Swim s2 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蛙泳...");
            }
        };

        s2.swimming();
        s2.swimming();
    }
}

3.9.5 匿名內部類的特點

  1. 定義一個沒有名字的內部類
  2. 這個類實現了父類,或者父類介面
  3. 匿名內部類會建立這個沒有名字的類的物件

3.9.6 匿名內部類的使用場景

通常在方法的形式引數是介面或者抽象類時,也可以將匿名內部類作為引數傳遞。程式碼如下:

interface Swim {
    public abstract void swimming();
}

public class Demo07 {
    public static void main(String[] args) {
        // 普通方式傳入物件
        // 建立實現類物件
        Student s = new Student();
        
        goSwimming(s);
        // 匿名內部類使用場景:作為方法引數傳遞
        Swim s3 = new Swim() {
            @Override
            public void swimming() {
                System.out.println("蝶泳...");
            }
        };
        // 傳入匿名內部類
        goSwimming(s3);

        // 完美方案: 一步到位
        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("大學生, 蛙泳...");
            }
        });

        goSwimming(new Swim() {
            public void swimming() {
                System.out.println("小學生, 自由泳...");
            }
        });
    }

    // 定義一個方法,模擬請一些人去游泳
    public static void goSwimming(Swim s) {
        s.swimming();
    }
}

相關文章