ch10_oop_advanced

小兵学习笔记發表於2024-09-13
  • 第10章 物件導向程式設計(高階部分)
    • 類變數和類方法
      • 類變數-提出問題
      • 類變數記憶體佈局
      • 什麼是類變數
      • 如何定義類變數
      • 如何訪問類變數
      • 類變數使用注意事項
      • 類方法基本介紹
      • 類方法的呼叫
      • 類方法經典的使用場景
      • 類方法使用注意事項和細節討論
    • 理解main 方法語法
      • 深入理解main 方法
      • 特別提示
    • 程式碼塊
      • 基本介紹
      • 基本語法
      • 程式碼塊的好處和案例演示
      • 程式碼塊使用注意事項和細節討論!!!
    • 單例設計模式
      • 什麼是設計模式
      • 什麼是單例模式
        • 餓漢式
        • 懶漢式
        • 比較
    • final 關鍵字
      • 基本介紹
      • final 使用注意事項和細節討論
    • 抽象類
      • 引出
      • 抽象類的介紹
      • 抽象類使用的注意事項和細節討論
    • 抽象類最佳實踐-模板設計模式
      • 基本介紹
      • 模板設計模式能解決的問題
      • 最佳實踐
    • 介面
      • 基本介紹
      • 深入討論
      • 注意事項和細節
      • 實現介面VS繼承類
      • 介面的多型特性
    • 內部類
      • 基本介紹
      • 基本語法
      • 內部類的分類
      • 區域性內部類的使用
      • 匿名內部類的使用!!!!!
      • 匿名內部類的最佳實踐
      • 成員內部類的使用
      • 靜態內部類的使用
      • 課堂測試題

第10章 物件導向程式設計(高階部分)

類變數和類方法

類變數-提出問題

在main方法中定義一個變數count,當一個小孩加入遊戲後count++,最後個count 就記錄有多少小孩玩遊戲 。

問題分析:

count是一個獨立於物件,很尷尬,以後我們訪問count很麻煩,沒有使用到OOP。因此,我們引出類變數/靜態變數。

package com.hspedu.static_;

public class ChildGame {

    public static void main(String[] args) {

        //定義一個變數 count, 統計有多少小孩加入了遊戲
        int count = 0;

        Child child1 = new Child("白骨精");
        child1.join();
        //count++;
        child1.count++;

        Child child2 = new Child("狐狸精");
        child2.join();
        //count++;
        child2.count++;

        Child child3 = new Child("老鼠精");
        child3.join();
        //count++;
        child3.count++;

        //===========
        // 類變數,可以透過類名來訪問
        System.out.println("共有" + Child.count  + " 小孩加入了遊戲...");
        // 下面的程式碼輸出什麼?
        System.out.println("child1.count=" + child1.count);//3
        System.out.println("child2.count=" + child2.count);//3
        System.out.println("child3.count=" + child3.count);//3



    }
}

class Child { //類
    private String name;
    // 定義一個變數 count ,是一個類變數(靜態變數) static 靜態!!!
    // 該變數最大的特點就是會被 Child 類的所有的物件例項共享!!!
    public static int count = 0;
    public Child(String name) {
        this.name = name;
    }
    public void join() {
        System.out.println(name + " 加入了遊戲..");
    }
}

類變數記憶體佈局

https://blog.csdn.net/x_iya/article/details/81260154/

https://www.zhihu.com/question/59174759/answer/163207831

有些書說在方法區... jdk 版本有關係,記住兩點:

(1) static變數是同一個類所有物件共享

(2) static類變數,在類載入的時候就生成了.靜態變數是類載入的時候,就建立了,所以不用建立物件例項也能直接透過 類名.類變數名 訪問。

什麼是類變數

類變數也叫靜態變數/靜態屬性,是該類的所有物件共享的變數,任何一個該類的物件去訪問它時,取到的都是相同的值,同樣任何一個該類的物件去修改它時,修改的也是同一個變數。這個從前面的圖也可看出來。

如何定義類變數

定義語法:

訪問修飾符static資料型別變數名;[推薦]

static訪問修飾符資料型別變數名;

如何訪問類變數

類名.類變數名

或者物件名.類變數名【靜態變數的訪問修飾符的訪問許可權和範圍和普通屬性是一樣的】

推薦使用:類名.類變數名;

類變數使用注意事項

1.什麼時候需要用類變數

當我們需要讓某個類的所有物件都共享一個變數時,就可以考慮使用類變數(靜態變數):比如:定義學生類,統計所有學生共交多少錢。Student (name, staticfee)

2.類變數與例項變數(普通屬性)區別

類變數是該類的所有物件共享的,而例項變數是每個物件獨享的。

3.加上static稱為類變數或靜態變數,否則稱為例項變數/普通變數/非靜態變數

4.類變數可以透過類名.類變數名或者物件名.類變數名來訪問,但java設計者推薦我們使用類名.類變數名方式訪問。【前提是滿足訪問修飾符的訪問許可權和範圍】

5.例項變數不能透過類名.類變數名方式訪問。

6.類變數是在類載入時就初始化了,也就是說,即使你沒有建立物件,只要類載入了.就可以使用類變數了。

7.類變數的生命週期是隨類的載入開始,隨著類消亡而銷燬。

類方法基本介紹

類方法也叫靜態方法。形式如下:

訪問修飾符 static 資料返回型別 方法名(){}【推薦】

static 訪問修飾符 資料返回型別 方法名(){}

類方法的呼叫

使用方式:

類名.類方法名或者物件名.類方法名

package com.hspedu.static_;

public class StaticMethod {
    public static void main(String[] args) {
        //建立2個學生物件,叫學費
        Stu tom = new Stu("tom");
        //tom.payFee(100);
        Stu.payFee(100);//對不對?對

        Stu mary = new Stu("mary");
        //mary.payFee(200);
        Stu.payFee(200);//對


        //輸出當前收到的總學費
        Stu.showFee();//300

        //如果我們希望不建立例項,也可以呼叫某個方法(即當做工具來使用)
        //這時,把方法做成靜態方法時非常合適
        System.out.println("9開平方的結果是=" + Math.sqrt(9));


        System.out.println(MyTools.calSum(10, 30));
    }
}
//開發自己的工具類時,可以將方法做成靜態的,方便呼叫
class MyTools  {
    //求出兩個數的和
    public static double calSum(double n1, double n2) {
        return  n1 + n2;
    }
    //可以寫出很多這樣的工具方法...
}
class Stu {
    private String name;//普通成員
    //定義一個靜態變數,來累積學生的學費
    private static double fee = 0;

    public Stu(String name) {
        this.name = name;
    }
    // 說明
    // 1. 當方法使用了static修飾後,該方法就是靜態方法
    // 2. 靜態方法就可以訪問靜態屬性/變數
    public static void payFee(double fee) {
        Stu.fee += fee;//累積到
    }
    public static void showFee() {
        System.out.println("總學費有:" + Stu.fee);
    }
}

類方法經典的使用場景

當方法中不涉及到任何和物件相關的成員,則可以將方法設計成靜態方法, 提高開發效率。

比如:

工具類中的方法utils。Math類、Arrays類、Collections集合類看下原始碼可以發現都是static方法。

類方法使用注意事項和細節討論

  1. 類方法和普通方法都是隨著類的載入而載入,將結構資訊儲存在方法區∶類方法中無this的引數。普通方法中隱含著this的引數。

  2. 類方法可以透過類名呼叫,也可以透過物件名呼叫。普通方法和物件有關,需要透過物件名呼叫,比如物件名.方法名(引數),不能透過類名呼叫。

  3. 類方法中不允許使用和物件有關的關鍵字,比如this和super。普通方法(成員方法)可以。

  4. 類方法(靜態方法)中只能訪問靜態變數或靜態方法。普通成員方法,既可以訪問非靜態成員,也可以訪問靜態成員!!

package com.hspedu.static_;

public class StaticMethodDetail {
    public static void main(String[] args) {

        D.hi();//ok
        //非靜態方法,不能透過類名呼叫
        //D.say();, 錯誤,需要先建立物件,再呼叫
        new D().say();//可以
    }
}
class D {

    private int n1 = 100;
    private static  int n2 = 200;
    public void say() {//非靜態方法,普通方法

    }

    public static  void hi() {//靜態方法,類方法
        //類方法中不允許使用和物件有關的關鍵字,
        //比如this和super。普通方法(成員方法)可以。
        //System.out.println(this.n1);
    }

    //類方法(靜態方法)中 只能訪問 靜態變數 或靜態方法
    //口訣:靜態方法只能訪問靜態成員.
    public static void hello() {
        System.out.println(n2);
        System.out.println(D.n2);
        //System.out.println(this.n2);不能使用
        hi();//OK
        //say();//錯誤
    }
    //普通成員方法,既可以訪問  非靜態成員,也可以訪問靜態成員
    //小結: 非靜態方法可以訪問 靜態成員和非靜態成員
    public void ok() {
        //非靜態成員
        System.out.println(n1);
        say();
        //靜態成員
        System.out.println(n2);
        hello();

    }
}

練習:

package com.hspedu.static_;

public class StaticExercise03 {
}

class Person {
    private int id;
    private static int total = 0;
    public static void setTotalPerson(int total){
        // this.total = total;//錯誤,因為在static方法中,不可以使用this 關鍵字

        Person.total = total;
    }
    public Person() {//構造器
        total++;
        id = total;
    }
    //編寫一個方法,輸出total的值
    public static void m() {
        System.out.println("total的值=" + total);
    }
}
class TestPerson {
    public static void main(String[] args) {

        Person.setTotalPerson(3); // 這裡沒有呼叫構造器
        new Person(); // new了之後呼叫構造器,count++
        Person.m();// 最後 total的值就是4
    }
}

注意:

Person.setTotalPerson(3); 呼叫靜態方法 這裡還沒有呼叫構造器

new Person(); new了之後才呼叫構造器,count++

因為構造器是在建立物件時完成對物件的初始化。

理解main 方法語法

深入理解main 方法

解釋main方法的形式: public static void main(String[] args){}

1.main方法時虛擬機器呼叫

2.java虛擬機器需要呼叫類的main()方法,所以該方法的訪問許可權必須是public

3.java虛擬機器在執行main()方法時不必建立物件,所以該方法必須是static

4.該方法接收String型別的陣列引數,該陣列中儲存執行java命令時傳遞給所執行的類的引數,案例演示,接收引數.

5.java執行的程式引數1引數2引數。

說明:在idea如何傳遞引數?

在Program arguments 中傳入引數即可。

特別提示

在main()方法中,我們可以直接呼叫main 方法所在類的靜態方法或靜態屬性。但是,不能直接訪問該類中的非靜態成員,必須建立該類的一個例項物件後,才能透過這個物件去訪問類中的非靜態成員。

程式碼塊

基本介紹

程式碼化塊又稱為初始化塊,屬於類中的成員[即是類的一部分],類似於方法,將邏輯語句封裝在方法體中,透過包圍起來。

但和方法不同,沒有方法名,沒有返回,沒有引數,只有方法體,而且不用透過物件或類顯式呼叫,而是載入類時,或建立物件時隱式呼叫

基本語法

[修飾符]{
   程式碼
};

說明注意;

  1. 修飾符可選,要寫的話,也只能寫static

  2. 程式碼塊分為兩類,使用static修飾的叫靜態程式碼塊,沒有static修飾的,叫普通程式碼塊/非靜態程式碼塊。

  3. 邏輯語句可以為任何邏輯語句(輸入、輸出、方法呼叫、迴圈、判斷等)

  4. ;號可以寫上,也可以省略。

程式碼塊的好處和案例演示

  1. 相當於另外一種形式的構造器(對構造器的補充機制),可以做初始化的操作

  2. 場景:如果多個構造器中都有重複的語句,可以抽取到初始化塊中,提高程式碼的重用性

這樣當我們不管呼叫哪個構造器,建立物件,都會先呼叫程式碼塊的內容,程式碼塊呼叫的順序優先於構造器。

package com.hspedu.codeblock_;

public class CodeBlock01 {
    public static void main(String[] args) {
        Movie movie = new Movie("你好,李煥英");
        System.out.println("===============");
        Movie movie2 = new Movie("唐探3", 100, "陳思誠");
    }
}

class Movie {
    private String name;
    private double price;
    private String director;

    // 3個構造器-》過載
    {
        System.out.println("電影螢幕開啟...");
        System.out.println("廣告開始...");
        System.out.println("電影正是開始...");
    };

    public Movie(String name) {
        System.out.println("Movie(String name) 被呼叫...");
        this.name = name;
    }

    public Movie(String name, double price) {

        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director) {

        System.out.println("Movie(String name, double price, String director) 被呼叫...");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

程式碼塊使用注意事項和細節討論!!!

  1. static程式碼塊也叫靜態程式碼塊,作用就是對類進行初始化,而且它隨著類的載入而執行,並且只會執行一次。如果是普通程式碼塊,每建立一個物件, 就執行一次。

  2. 類什麼時候被載入

    • 建立物件例項時(new)
    • 建立子類物件例項,父類也會被載入
    • 使用類的靜態成員時(靜態屬性,靜態方法)
  3. 普通的程式碼塊,在建立物件例項時,會被隱式的呼叫。被建立一次,就會呼叫一次。

    如果只是使用類的靜態成員時,普通程式碼塊並不會執行。(沒有建立物件例項)

  4. 建立一個物件時,在一個類呼叫順序是 (重點,難點)

    1. 呼叫靜態程式碼塊和靜態屬性初始化 (注意:靜態程式碼塊和靜態屬性初始化呼叫的優先順序一樣,如果有多個靜態程式碼塊和多個靜態變數初始化,則按他們定義的先後順序呼叫)

    2. 呼叫普通程式碼塊和普通屬性的初始化(注意:普通程式碼塊和普通屬性初始化呼叫的優先順序一樣,如果有多個普通程式碼塊和多個普通屬性初始化,則按定義先後順序呼叫)

    3. 呼叫構造方法

  5. 構造器的最前面其實隱含了super()和呼叫普通程式碼塊, 靜態相關的程式碼塊,屬性初始化,在類載入時,就執行完畢,因此是優先於構造器和普通程式碼塊執行的。

  6. 我們看一下建立一個子類物件時(繼承關係),他們的呼叫順序如下:

    1. 父類的靜態程式碼塊和靜態屬性(優先順序一樣,按定義順序執行)(類載入)

    2. 子類的靜態程式碼塊和靜態屬性(優先順序一樣,按定義順序執行)(類載入)

    3. 父類的普通程式碼塊和普通屬性初始化(優先順序一樣,按定義順序執行)

    4. 父類的構造方法

    5. 子類的普通程式碼塊和普通屬性初始化(優先順序一樣,按定義順序執行)

    6. 子類的構造方法

  7. 靜態程式碼塊(本質上是靜態方法)只能直接呼叫靜態成員(靜態屬性和靜態方法),普通程式碼塊(本質上是普通方法)可以呼叫任意成員。

package com.hspedu.codeblock_;

public class CodeBlockDetail04 {
    public static void main(String[] args) {
        //老師說明
        //(1) 進行類的載入
        //1.1 先載入 父類 A02 1.2 再載入 B02
        //(2) 建立物件
        //2.1 從子類的構造器開始
        //new B02();//物件

        new C02();
    }
}

class A02 { //父類
    private static int n1 = getVal01();
    static {
        System.out.println("A02的一個靜態程式碼塊..");//(2)
    }
    {
        System.out.println("A02的第一個普通程式碼塊..");//(5)
    }
    pulic int n3 = getVal02();//普通屬性的初始化
    public static int getVal01() {
        System.out.println("getVal01");//(1)
        return 10;
    }

    public int getVal02() {
        System.out.println("getVal02");//(6)
        return 10;
    }

    public A02() {//構造器
        //隱藏
        //super()
        //普通程式碼和普通屬性的初始化......
        System.out.println("A02的構造器");//(7)
    }

}

class C02 {
    private int n1 = 100;
    private static  int n2 = 200;

    private void m1() {

    }
    private static void m2() {

    }

    static {
        //靜態程式碼塊,只能呼叫靜態成員
        //System.out.println(n1);錯誤
        System.out.println(n2);//ok
        //m1();//錯誤
        m2();
    }
    {
        //普通程式碼塊,可以使用任意成員
        System.out.println(n1);
        System.out.println(n2);//ok
        m1();
        m2();
    }
}

class B02 extends A02 { //

    private static int n3 = getVal03();

    static {
        System.out.println("B02的一個靜態程式碼塊..");//(4)
    }
    public int n5 = getVal04();
    {
        System.out.println("B02的第一個普通程式碼塊..");//(9)
    }

    public static int getVal03() {
        System.out.println("getVal03");//(3)
        return 10;
    }

    public int getVal04() {
        System.out.println("getVal04");//(8)
        return 10;
    }
    //一定要慢慢的去品..
    public B02() {//構造器
        //隱藏了
        //super()
        //普通程式碼塊和普通屬性的初始化...
        System.out.println("B02的構造器");//(10)
        // TODO Auto-generated constructor stub
    }
}

練習:

package com.hspedu.codeblock_;

public class CodeBlockExercise02 {
}

class Sample
{
    Sample(String s)
    {
        System.out.println(s);
    }
    Sample()
    {
        System.out.println("Sample預設建構函式被呼叫");
    }
}
class Test{
    Sample sam1=new Sample("sam1成員初始化");//
    static Sample sam=new Sample("靜態成員sam初始化 ");//
    static{
        System.out.println("static塊執行");//
        if(sam==null)System.out.println("sam is null");
    }
    Test()//構造器
    {
        System.out.println("Test預設建構函式被呼叫");//
    }
    //主方法
    public static void  main(String  str[])
    {
        Test a=new Test();//無參構造器
    }

}

1. 靜態成員sam 初始化
2. static 塊執行
3. sam1 成員初始化
4. Test 預設建構函式被呼叫

單例設計模式

什麼是設計模式

靜態方法和屬性的經典使用

設計模式是在大量的實踐中總結和理論化之後優選的程式碼結構、程式設計風格、以及解決問題的思考方式。設計模式就像是經典的棋譜,不同的棋局,我們用不同的棋譜,免去我們自己再思考和摸索。

什麼是單例模式

  1. 所謂類的單例設計模式,就是採取一定的方法保證在整個的軟體系統中,對某個類只能存在一個物件例項,並且該類只提供一個取得其物件例項的方法。

  2. 單例模式有兩種方式:1) 餓漢式 2) 懶漢式

餓漢式

步驟如下:

  1. 構造器私有化 =》防止直接new

  2. 類的內部建立物件

  3. 向外暴露一個靜態的公共方法。getlnstance

餓漢式:有可能還沒有用到這個物件,但是由於類的機制已經將物件建立好了。線上程還沒出現之前就已經例項化了,因此餓漢式執行緒一定是安全的。

package com.hspedu.single_;

public class SingleTon01 {

    public static void main(String[] args) {
//        GirlFriend xh = new GirlFriend("小紅");
//        GirlFriend xb = new GirlFriend("小白");

        //透過方法可以獲取物件
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        // 都是同一個物件
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);

        System.out.println(instance == instance2);// T 同一個物件
        //System.out.println(GirlFriend.n1);
    }
}

// 有一個類, GirlFriend
// 只能有一個女朋友
class GirlFriend {

    private String name;
    // public static  int n1 = 100;
    // 為了能夠在靜態方法中,返回 gf物件,需要將其修飾為static
    // 物件,通常是重量級的物件, 餓漢式可能造成建立了物件,但是沒有使用.
    // 只要類載入了,就一定建立了gf物件
    private static GirlFriend gf = new GirlFriend("小紅紅");

    // 如何保障我們只能建立一個 GirlFriend 物件
    // 步驟[單例模式-餓漢式]
    // 1. 將構造器私有化
    // 2. 在類的內部直接建立物件(該物件是static)
    // 3. 提供一個公共的static方法,返回 gf 物件
    private GirlFriend(String name) {
        System.out.println("構造器被呼叫.");
        this.name = name;
    }

    // 用static的目的就是在不建立物件的前提下直接呼叫
    public static GirlFriend getInstance() {
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

懶漢式

懶漢式,只有當使用者使用getInstance時,才返回cat物件, 後面再次呼叫時,會返回上次建立的cat物件。

懶漢式可能會存線上程安全的問題。

package com.hspedu.single_;

/**
 * 演示懶漢式的單例模式
 */
public class SingleTon02 {
    public static void main(String[] args) {
        //new Cat("大黃");
        //System.out.println(Cat.n1);
        Cat instance = Cat.getInstance();
        System.out.println(instance);


        //再次呼叫getInstance
        Cat instance2 = Cat.getInstance();
        System.out.println(instance2);

        System.out.println(instance == instance2);//T

    }
}


//希望在程式執行過程中,只能建立一個Cat物件
//使用單例模式
class Cat {
    private String name;
    public static  int n1 = 999;
    private static Cat cat ; //預設是null

    //步驟
    //1.仍然構造器私有化
    //2.定義一個static靜態屬性物件
    //3.提供一個public的static方法,可以返回一個Cat物件
    //4.懶漢式,只有當使用者使用getInstance時,才返回cat物件, 後面再次呼叫時,會返回上次建立的cat物件
    //  從而保證了單例
    private Cat(String name) {
        System.out.println("構造器呼叫...");
        this.name = name;
    }
    public static Cat getInstance() {

        if(cat == null) {//如果還沒有建立cat物件
            cat = new Cat("小可愛");
        }
        return cat;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

比較

  1. 二者最主要的區別在於建立物件的時機不同:餓漢式是在類載入就建立了物件例項,而懶漢式是在使用時才建立。

  2. 餓漢式不存線上程安全問題,懶漢式存線上程安全問題。(後面學習執行緒後,會完善一把)。

  3. 餓漢式存在浪費資源的可能。因為如果程式設計師一個物件例項都沒有使用,那麼餓漢式建立的物件就浪費了,懶漢式是使用時才建立,就不存在這個問題。

  4. 在我們javaSE標準類中,java.lang.Runtime就是經典的單例模式.

final 關鍵字

基本介紹

final中文意思:最後的,最終的.

final可以修飾類、屬性、方法和區域性變數

在某些情況下,程式設計師可能有以下需求,就會使用到final:

  1. 當不希望類被繼承時,可以用final修飾.

  2. 當不希望父類的某個方法被子類覆蓋/重寫(override)時,可以用final關鍵字修飾。【案例演示:訪問修飾符final返回型別方法名】

  3. 當不希望類的的某個屬性的值被修改,可以用final修飾.(例如: public final double TAX RATE=0.08)

  4. 當不希望某個區域性變數被修改,可以使用final修飾(例如: final double TAX RATE=0.08)

final 使用注意事項和細節討論

  1. final修飾的屬性又叫常量,一般用 XX_XX_XX (大寫)來命名

  2. final修飾的屬性在定義時,必須賦初值,並且以後不能再修改,賦值可以在如下位置之一:

    定義時:如public final double TAX_RATE=0.08;

    在構造器中

    在程式碼塊中

    class AA {
    /*
    1. 定義時:如public final double TAX_RATE=0.08;
    2. 在構造器中
    3. 在程式碼塊中
    */
    public final double TAX_RATE = 0.08;//1.定義時賦值
    public final double TAX_RATE2 ;
    public final double TAX_RATE3 ;
    public AA() {//構造器中賦值
        TAX_RATE2 = 1.1;
        }
        {//在程式碼塊賦值
            TAX_RATE3 = 8.8;
        }
    }
    
  3. 如果final修飾的屬性是靜態的,則初始化的位置只能是

    ①定義時

    ②在靜態程式碼塊(不能在構造器中賦值。因為構造器是在物件建立的時候才會進行賦值)

  4. final類不能繼承,但是可以例項化物件。(例項化沒問題)

  5. 如果類不是final類,但是含有final方法,則該方法雖然不能重寫,但是可以被繼承。(子類用是沒問題的,雖然不能重寫)

  6. 一般來說,如果一個類已經是final類了,就沒有必要再將方法修飾成final方法。(因為類既然不能被繼承,也就相應無法被重寫)。

  7. final不能修飾構造方法(即構造器)。

  8. final和static 往往搭配使用,效率更高,因為不會導致類載入,底層編譯器做了最佳化處理。

  9. 包裝類(Integer,Double,Float,Boolean等都是final),String也是final類。

抽象類

引出

當父類的某些方法,需要宣告,但是又不確定如何實現時,可以將其宣告為抽象方法,那麼這個類就是抽象類。

所謂抽象方法就是沒有實現的方法,所謂沒有實現就是指,沒有方法體。

當一個類中存在抽象方法時,需要將該類宣告為abstract 類,一般來說,抽象類會被繼承,由其子類來實現抽象方法。

abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }
    public abstract void eat()  ;
}

抽象類的介紹

1)用abstract關鍵字來修飾一個類時,這個類就叫抽象類訪問修飾符

2)用abstract關鍵字來修飾一個方法時,這個方法就是抽象方法

訪問修飾符 abstract 返回型別 方法名(引數列表);//沒有方法體

3)抽象類的價值更多作用是在於設計,是設計者設計好後,讓子類繼承並實現抽象類。

4)抽象類是考官比較愛問的知識點,在框架和設計模式使用較多。

抽象類使用的注意事項和細節討論

1)抽象類不能被例項化

2)抽象類不一定要包含abstract方法。也就是說, 抽象類可以沒有abstract方法。

3)一旦類包含了abstract方法,則這個類必須宣告為abstract。

4)abstract只能修飾類和方法,不能修飾屬性和其它的。

5)抽象類可以有任意成員【抽象類本質還是類】,比如: 非抽象方法、構造器、靜態屬性等等。

6)抽象方法不能有主體,即不能實現

7)如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法,除非它自己也宣告為abstract類。

8)抽象方法不能使用private、final和 static來修飾,因為這些關鍵字都是和重寫相違背的。

抽象類最佳實踐-模板設計模式

基本介紹

抽象類體現的就是一種模板模式的設計,抽象類作為多個子類的通用模板,子類在抽象類的基礎上進行擴充套件、改造,但子類總體上會保留抽象類的行為方式。

模板設計模式能解決的問題

1)當功能內部一部分實現是確定,一部分實現是不確定的。這時可以把不確定的部分暴露出去,讓子類去實現。
2)編寫一個抽象父類,父類提供了多個子類的通用方法,並把一個或多個方法留給其子類實現,就是一種模板模式.

最佳實踐

需求:

有多個類,完成不同的任務job

要求統計得到各自完成任務的時間

package com.hspedu.abstract_;

abstract public class Template { //抽象類-模板設計模式

    public abstract void job();//抽象方法

    public void calculateTime() {//實現方法,呼叫job方法
        //得到開始的時間
        long start = System.currentTimeMillis();
        job(); //動態繫結機制
        //得的結束的時間
        long end = System.currentTimeMillis();
        System.out.println("任務執行時間 " + (end - start));
    }
}

以上就是把不確定的部分暴露出去,讓子類去實現。

介面

基本介紹

介面就是給出一些沒有實現的方法,封裝到一起,到某個類要使用的時候,在根據具體情況把這些方法寫出來。語法:

interface 介面名{
    //屬性
    //抽象方法(介面中可以省略abstract關鍵字)(在jdk8後還可以有靜態方法和預設方法)
}

class 類名 implements 介面 {
    // 自己屬性;
    // 自己方法;
    // 必須實現的介面的抽象方法
}

小結:

介面是更加抽象的類。抽象類裡的方法可以有方法體,介面裡的所有方法都沒有方法體(jdk7.0)。介面體現了程式設計的多型和高內聚低偶合的設計思想。

特別說明:Jdk8.0後介面類可以有靜態方法(static),預設方法(default),也就是說介面中可以有方法的具體實現入。

深入討論

說現在有一個專案經理(段玉),管理三個程式設計師,功能開發一個軟體.為了控制和管理軟體,專案經理可以定義一些介面,然後由程式設計師具體實現。

透過介面,不僅可以統一方法名,同時在呼叫時只需要根據介面識別即可。

package com.hspedu.interface_;

public interface DBInterface { //專案經理

    public void connect();//連線方法
    public void close();//關閉連線
}
package com.hspedu.interface_;
//A程式
public class MysqlDB implements DBInterface {
    @Override
    public void connect() {
        System.out.println("連線mysql");
    }

    @Override
    public void close() {
        System.out.println("關閉mysql");
    }
}
package com.hspedu.interface_;

//B程式設計師連線Oracle
public class OracleDB implements DBInterface{

    @Override
    public void connect() {
        System.out.println("連線oracle");
    }

    @Override
    public void close() {
        System.out.println("關閉oracle");
    }
}
package com.hspedu.interface_;

public class Interface03 {
    public static void main(String[] args) {

        MysqlDB mysqlDB = new MysqlDB();
        t(mysqlDB);
        OracleDB oracleDB = new OracleDB();
        t(oracleDB);
    }

    public static void t(DBInterface db) {
        db.connect();
        db.close();
    }
}

注意事項和細節

  1. 介面不能被例項化(new)

  2. 介面中所有的方法是public方法,介面中抽象方法,可以不用abstract修
    飾。void aaa(); 實際上是abstract void aa();(同理,不寫public也是預設public方法,因此實現時該方法不寫public會報錯。)

  3. 一個普通類實現介面,就必須將該介面的所有方法都實現。

  4. 抽象類實現介面,可以不用實現介面的方法。

  5. 一個類同時可以實現多個介面

    class Timer implements IA, IB{ }
    
  6. 介面中的屬性,只能是final的,而且是 public static final修飾符。比如:int a=1;實際上是public static final int a=1; (必須初始化)

  7. 介面中屬性的訪問形式:介面名.屬性名

  8. 介面不能繼承其它的類,但是可以繼承多個別的介面。(介面無法實現介面)

    interface A extends B,C{}
    
  9. 介面的修飾符只能是public和預設,這點和類的修飾符是一樣的。

實現介面VS繼承類

當子類繼承了父類,就自動的擁有父類的功能,如果子類需要擴充套件功能,可以透過實現介面的方式擴充套件。可以理解 實現介面 是對 java 單繼承機制的一種補充。

  1. 介面和繼承解決的問題不同

    繼承的價值主要在於:解決程式碼的複用性和可維護性。

  2. 介面的價值主要在於:設計,設計好各種規範(方法),讓其它類去實現這些方法。即更加的靈活

介面比繼承更加靈活:繼承是滿足is - a的關係,而介面只需滿足 like - a的關係。

介面在一定程度上實現程式碼解耦[即:介面規範性+動態繫結機制]

介面的多型特性

  1. 多型引數

    在前面的Usb介面案例,UsbInterface usb,既可以接收手機物件,又可以接收相機物件,就體現了介面多型(介面引用可以指向實現了介面的類的物件)。

    package com.hspedu.interface_;
    
    public class InterfacePolyParameter {
        public static void main(String[] args) {
    
            //介面的多型體現
            //介面型別的變數 if01 可以指向 實現了IF介面類的物件例項
            IF if01 = new Monster();
            if01 = new Car();
    
            // 繼承體現的多型
            // 父類型別的變數 a 可以指向 繼承AAA的子類的物件例項
            AAA a = new BBB();
            a = new CCC();
        }
    }
    
    interface IF {}
    class Monster implements IF{}
    class Car implements  IF{}
    
    class AAA {
    
    }
    class BBB extends AAA {}
    class CCC extends AAA {}
    
2. 多型陣列

演示一個案例:給**Usb陣列中,存放 Phone 和相機物件**,Phone類還有一個特有的方法call(),請遍歷Usb陣列,如果是Phone物件,除了呼叫Usb介面定義的方法外,還需要呼叫Phone特有方法call。

```java
package com.hspedu.interface_;

public class InterfacePolyArr {
    public static void main(String[] args) {

        //多型陣列 -> 介面型別陣列
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();
        usbs[1] = new Camera_();
        /*
        給Usb陣列中,存放 Phone 和 相機物件,Phone類還有一個特有的方法call(),
        請遍歷Usb陣列,如果是Phone物件,除了呼叫Usb 介面定義的方法外,
        還需要呼叫Phone 特有方法 call
         */
        for(int i = 0; i < usbs.length; i++) {
            usbs[i].work();//動態繫結..
            //和前面一樣,我們仍然需要進行型別的向下轉型
            if(usbs[i] instanceof Phone_) {//判斷他的執行型別是 Phone_
                ((Phone_) usbs[i]).call();
            }
        }
    }
}

interface Usb{
    void work();
}
class Phone_ implements Usb {
    public void call() {
        System.out.println("手機可以打電話...");
    }

    @Override
    public void work() {
        System.out.println("手機工作中...");
    }
}
class Camera_ implements Usb {

    @Override
    public void work() {
        System.out.println("相機工作中...");
    }
}
  1. 介面存在多型傳遞現象

    package com.hspedu.interface_;
    
    /**
     * 演示多型傳遞現象
     */
    public class InterfacePolyPass {
        public static void main(String[] args) {
            //介面型別的變數可以指向,實現了該介面的類的物件例項
            IG ig = new Teacher();
            //如果IG 繼承了 IH 介面,而Teacher 類實現了 IG介面
            //那麼,實際上就相當於 Teacher 類也實現了 IH介面.
            //這就是所謂的 介面多型傳遞現象.
            IH ih = new Teacher();
        }
    }
    
    interface IH {
        void hi();
    }
    interface IG extends IH{ }
    class Teacher implements IG {
        @Override
        public void hi() {
        }
    }
    

內部類

如果定義類在區域性位置(方法中/程式碼塊) (1) 區域性內部類 (2) 匿名內部類
定義在成員位置 (1) 成員內部類 (2) 靜態內部類

基本介紹

一個類的內部又完整的巢狀了另一個類結構。被巢狀的類稱為內部類(inner class),巢狀其他類的類稱為外部類(outer class)。

是我們類的第五大成員(類的五大成員:屬性、方法、構造器、程式碼塊、內部類),內部類最大的特點就是可以直接訪問私有屬性,並且可以體現類與類之間的包含關係,注意:內部類是學習的難點,同時也是重點,後面看底層原始碼時,有大量的內部類。

基本語法

class Outer{ // 外部類
    class Inner{
        // 內部類
        }
}
class Other{// 外部其他類
}

內部類的分類

定義在外部類區域性位置上( 比如方法內 ):

  1. 區域性內部類 ( 有類名 )

  2. 匿名內部類 ( 沒有類名,重點!!!!!!!! )

定義在外部類的成員位置上:

  1. 成員內部類 ( 沒用 static 修飾 )

  2. 靜態內部類 ( 使用 static 修飾 )

區域性內部類的使用

說明:區域性內部類是定義在外部類的區域性位置,比如方法中,並且有類名

1.可以直接訪問外部類的所有成員,包含私有的。

2.不能新增訪問修飾符,因為它的地位就是一個區域性變數。區域性變數是不能使用修飾符的。但是可以使用final修飾,因為區域性變數也可以使用final。

3.作用域:僅僅在定義它的方法或程式碼塊中

4.區域性內部類訪問外部類的成員[訪問方式:直接訪問]

5.外部類訪問區域性內部類的成員

訪問方式: 建立物件,再訪問 (注意:必須在作用域內)

小結:

(1)區域性內部類定義在方法中/程式碼塊
(2)作用域在方法體或者程式碼塊中
(3)本質仍然是一個類

6.外部其他類不能訪問區域性內部類(因為區域性內部類地位是一個區域性變數)。

7.如果外部類和區域性內部類的成員重名時,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問。

這裡 外部類名.this 本質上就是外部類的物件,即哪個物件呼叫了n2,那麼 外部類名.this 就指向哪個物件。

System.out.printin(""外部類的n2=”+外部類名.this.n2);
package com.hspedu.innerclass;
/**
 * 演示區域性內部類的使用
 */
public class LocalInnerClass {//
    public static void main(String[] args) {
        //演示一遍
        Outer02 outer02 = new Outer02();
        outer02.m1();
        System.out.println("outer02的hashcode=" + outer02);
    }
}


class Outer02 {//外部類
    private int n1 = 100;
    private void m2() {
        System.out.println("Outer02 m2()");
    }//私有方法
    public void m1() {//方法
        //1.區域性內部類是定義在外部類的區域性位置,通常在方法
        //3.不能新增訪問修飾符,但是可以使用final 修飾
        //4.作用域 : 僅僅在定義它的方法或程式碼塊中
        final class Inner02 {//區域性內部類(本質仍然是一個類)
            //2.可以直接訪問外部類的所有成員,包含私有的
            private int n1 = 800;
            public void f1() {
                //5. 區域性內部類可以直接訪問外部類的成員,比如下面 外部類n1 和 m2()
                //7. 如果外部類和區域性內部類的成員重名時,預設遵循就近原則,如果想訪問外部類的成員,
                //   使用 外部類名.this.成員)去訪問
                //  Outer02.this 本質就是外部類的物件, 即哪個物件呼叫了m1, Outer02.this就是哪個物件
                System.out.println("n1=" + n1 + " 外部類的n1=" + Outer02.this.n1);
                System.out.println("Outer02.this hashcode=" + Outer02.this);
                m2();
            }
        }
        //6. 外部類在方法中,可以建立Inner02物件,然後呼叫方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

匿名內部類的使用!!!!!

(1)本質是類 (2) 內部類 (3) 該類沒有名字 (4) 同時還是一個物件

說明: 匿名內部類是定義在外部類的區域性位置, 比如方法中, 並且沒有類名

1.匿名內部類的基本語法

new 類或介面 (引數列表){
   類體
);
package com.hspedu.innerclass;


/**
 * 演示匿名內部類的使用
 */
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}

class Outer04 { //外部類
    private int n1 = 10;//屬性
    public void method() {//方法
        //基於!!!介面!!!的匿名內部類
        //解讀
        //1.需求:想使用IA介面,並建立物件
        //2.傳統方式,是寫一個類,實現該介面,並建立物件
        //3.需求是 Tiger/Dog 類只是使用一次,後面再不使用
        //4. 可以使用匿名內部類來簡化開發
        //5. tiger的編譯型別 ? IA
        //6. tiger的執行型別 ? 就是匿名內部類  Outer04$1
        /*
            我們看底層 會分配 類名 Outer04$1
            class Outer04$1 implements IA {
                @Override
                public void cry() {
                    System.out.println("老虎叫喚...");
                }
            }
         */
        //7. jdk底層在建立匿名內部類 Outer04$1,立即馬上就建立了 Outer04$1例項,並且把地址
        //   返回給 tiger
        //8. 匿名內部類使用一次,就不能再使用, 但是tiger這個物件就沒有限制了。
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫喚...");
            }
        };
        System.out.println("tiger的執行型別=" + tiger.getClass());
        tiger.cry();
        tiger.cry();
        tiger.cry();

//        IA tiger = new Tiger();
//        tiger.cry();

        // 演示基於!!!類!!!的匿名內部類
        //分析
        //1. father 編譯型別 Father
        //2. father 執行型別 Outer04$2
        //3. 底層會建立匿名內部類
        /*
            具體的實現程式碼與註釋中的程式碼近似等價
            class Outer04$2 extends Father{
                @Override
                public void test() {
                    System.out.println("匿名內部類重寫了test方法");
                }
            }
         */
        //4. 同時也直接返回了 匿名內部類 Outer04$2的物件
        //5. 注意("jack") 引數列表會傳遞給 Father 構造器
        Father father = new Father("jack"){
            @Override
            public void test() {
                System.out.println("匿名內部類重寫了test方法");
            }
        };
        System.out.println("father物件的執行型別=" + father.getClass());//Outer04$2
        father.test();

        //基於!!!抽象類!!!的匿名內部類
        Animal animal = new Animal(){
            @Override
            void eat() {
                System.out.println("小狗吃骨頭...");
            }
        };
        animal.eat();
    }
}

interface IA {//介面
    public void cry();
}
//class Tiger implements IA {
//
//    @Override
//    public void cry() {
//        System.out.println("老虎叫喚...");
//    }
//}
//class Dog implements  IA{
//    @Override
//    public void cry() {
//        System.out.println("小狗汪汪...");
//    }
//}

class Father { //類
    public Father(String name) { //構造器
        System.out.println("接收到name=" + name);
    }
    public void test() { //方法
    }
}

abstract class Animal { //抽象類
    abstract void eat();
}

2.匿名內部類的語法比較奇特,因為匿名內部類既是一個類的定義.同時它本身也是一個物件,因此從語法上看,它既有定義類的特徵,也有建立物件的特徵,對前面程式碼分析可以看出這個特點,因此可以呼叫匿名內部類方法。

3.可以直接訪問外部類的所有成員,包含私有的。

4、不能新增訪問修飾符,因為它的地位就是一個區域性變數。

5.作用域:僅僅在定義它的方法或程式碼塊中。

6.匿名內部類---訪問---->外部類成員[訪問方式:直接訪問]

7.外部其他類---不能訪問----->匿名內部類(因為匿名內部類地位是一個區域性變數)

8.如果外部類和匿名內部類的成員重名時,匿名內部類訪問的話,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問

package com.hspedu.innerclass;

public class AnonymousInnerClassDetail {
    public static void main(String[] args) {

        Outer05 outer05 = new Outer05();
        outer05.f1();
        //外部其他類---不能訪問----->匿名內部類
        System.out.println("main outer05 hashcode=" + outer05);
    }
}

class Outer05 {
    private int n1 = 99;

    public void f1() {
        //建立一個基於類的匿名內部類
        //不能新增訪問修飾符,因為它的地位就是一個區域性變數
        //作用域 : 僅僅在定義它的方法或程式碼塊中
        Person p = new Person(){
            private int n1 = 88;
            @Override
            public void hi() {
                // 可以直接訪問外部類的所有成員,包含私有的
                // 如果外部類和匿名內部類的成員重名時,匿名內部類訪問的話,
                // 預設遵循就近原則,如果想訪問外部類的成員,則可以使用 (外部類名.this.成員)去訪問
                System.out.println("匿名內部類重寫了 hi方法 n1=" + n1 +
                        " 外部內的n1=" + Outer05.this.n1 );
                // Outer05.this 就是呼叫 f1的 物件
                System.out.println("Outer05.this hashcode=" + Outer05.this);
            }
        };
        p.hi();//動態繫結, 執行型別是 Outer05$1

        //也可以直接呼叫, 匿名內部類本身也是返回物件
        // class 匿名內部類 extends Person {}
//        new Person(){
//            @Override
//            public void hi() {
//                System.out.println("匿名內部類重寫了 hi方法,哈哈...");
//            }
//            @Override
//            public void ok(String str) {
//                super.ok(str);
//            }
//        }.ok("jack");


    }
}

class Person {//類
    public void hi() {
        System.out.println("Person hi()");
    }
    public void ok(String str) {
        System.out.println("Person ok() " + str);
    }
}
//抽象類/介面...

匿名內部類的最佳實踐

當做實參直接傳遞,簡潔高效。

package com.hspedu.innerclass;

import com.hspedu.abstract_.AA;

public class InnerClassExercise01 {
    public static void main(String[] args) {

        //當做實參直接傳遞,簡潔高效
        f1(new IL() {
            @Override
            public void show() {
                System.out.println("這是一副名畫~~...");
            }
        });
        //傳統方法
        f1(new Picture());
    }

    //靜態方法,形參是介面型別
    public static void f1(IL il) {
        il.show();
    }
}

//介面
interface IL {
    void show();
}


//類->實現IL => 程式設計領域 (硬編碼)
class Picture implements IL {

    @Override
    public void show() {
        System.out.println("這是一副名畫XX...");
    }
}

有一個鈴聲介面Bell,裡面有個ring方法。有一個手機類Cellphone,具有鬧鐘功能alarmclock,引數是Bell型別。測試手機類的鬧鐘功能,透過匿名內部類(物件)作為引數,列印:懶豬起床了。再傳入另一個匿名內部類(物件),列印:小夥伴上課了

package com.hspedu.innerclass;

public class InnerClassExercise02 {
    public static void main(String[] args) {
        /*
        1.有一個鈴聲介面Bell,裡面有個ring方法。(右圖)
        2.有一個手機類Cellphone,具有鬧鐘功能alarmClock,引數是Bell型別(右圖)
        3.測試手機類的鬧鐘功能,透過匿名內部類(物件)作為引數,列印:懶豬起床了
        4.再傳入另一個匿名內部類(物件),列印:小夥伴上課了
         */
        CellPhone cellPhone = new CellPhone();
        //老韓解讀
        //1. 傳遞的是實現了 Bell介面的匿名內部類 InnerClassExercise02$1
        //2. 重寫了 ring
        //3. Bell bell = new Bell() {
        //            @Override
        //            public void ring() {
        //                System.out.println("懶豬起床了");
        //            }
        //        }
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("懶豬起床了");
            }
        });

        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小夥伴上課了");
            }
        });
    }
}
interface Bell{ //介面
    void ring();//方法
}
class CellPhone{//類
    public void alarmClock(Bell bell){//形參是Bell介面型別
        System.out.println(bell.getClass());
        bell.ring();//動態繫結
    }
}

成員內部類的使用

說明: 成員內部類是定義在外部類的成員位置,並且沒有static修飾。

1.可以直接訪問外部類的所有成員,包含私有的。

2.可以新增任意訪問修飾符(public、protected、預設、private), 因為它的地
位就是一個成員。

3.作用域和外部類的其他成員一樣,為整個類體比如前面案例,在外部類的成員方法中建立成員內部類物件,再呼叫方法。

4.成員內部類---訪問---->外部類成員(比如:屬性) 訪問方式:直接訪問

5.外部類---訪問------>成員內部類(說明) 訪問方式:建立物件,再訪問

6.外部其他類---訪問---->成員內部類

7.如果外部類和內部類的成員重名時,內部類訪問的話,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)去訪問

package com.hspedu.innerclass;

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();

        //外部其他類,使用成員內部類的三種方式
        // 第一種方式
        // outer08.new Inner08(); 相當於把 new Inner08()當做是outer08成員
        // 這就是一個語法,不要特別的糾結.
        Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();
        // 第二方式 在外部類中,編寫一個方法,可以返回 Inner08物件
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();


    }
}

class Outer08 { //外部類
    private int n1 = 10;
    public String name = "張三";

    private void hi() {
        System.out.println("hi()方法...");
    }

    //1.注意: 成員內部類,是定義在外部內的成員位置上
    //2.可以新增任意訪問修飾符(public、protected 、預設、private),因為它的地位就是一個成員
    public class Inner08 {//成員內部類
        private double sal = 99.8;
        private int n1 = 66;
        public void say() {
            //可以直接訪問外部類的所有成員,包含私有的
            //如果成員內部類的成員和外部類的成員重名,會遵守就近原則.
            //,可以透過  外部類名.this.屬性 來訪問外部類的成員
            System.out.println("n1 = " + n1 + " name = " + name + " 外部類的n1=" + Outer08.this.n1);
            hi();
        }
    }
    //方法,返回一個Inner08例項
    public Inner08 getInner08Instance(){
        return new Inner08();
    }


    //寫方法
    public void t1() {
        //使用成員內部類
        //建立成員內部類的物件,然後使用相關的方法
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println(inner08.sal);
    }
}


靜態內部類的使用

說明:靜態內部類是定義在外部類的成員位置, 並且有static修飾

1.可以直接訪問外部類的所有靜態成員,包含私有的,但不能直接訪問非靜態成員。

2.可以新增任意訪問修飾符(public. protected、預設、private),因為它的地位就是一個成員。

3.作用域:同其他的成員,為整個類體。

4.靜態內部類---訪問---->外部類(比如:靜態屬性)[訪問方式:直接訪問所有靜態成員]。

5.外部類---訪問------>靜態內部類 訪問方式:建立物件,再訪問。

6.外部其他類---訪問----->靜態內部類。

7.如果外部類和靜態內部類的成員重名時,靜態內部類訪問的時,預設遵循就近原則,如果想訪問外部類的成員,則可以使用(外部類名.成員)去訪向。

package com.hspedu.innerclass;

public class StaticInnerClass01 {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();

        //外部其他類 使用靜態內部類
        //方式1
        //因為靜態內部類,是可以透過類名直接訪問(前提是滿足訪問許可權)
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();
        //方式2
        //編寫一個方法,可以返回靜態內部類的物件例項.
        Outer10.Inner10 inner101 = outer10.getInner10();
        System.out.println("============");
        inner101.say();

        Outer10.Inner10 inner10_ = Outer10.getInner10_();
        System.out.println("************");
        inner10_.say();
    }
}

class Outer10 { //外部類
    private int n1 = 10;
    private static String name = "張三";
    private static void cry() {}
    //Inner10就是靜態內部類
    //1. 放在外部類的成員位置
    //2. 使用static 修飾
    //3. 可以直接訪問外部類的所有靜態成員,包含私有的,但不能直接訪問非靜態成員
    //4. 可以新增任意訪問修飾符(public、protected 、預設、private),因為它的地位就是一個成員
    //5. 作用域 :同其他的成員,為整個類體
    static class Inner10 {
        private static String name = "Timerring";
        public void say() {
            //如果外部類和靜態內部類的成員重名時,靜態內部類訪問的時,
            //預設遵循就近原則,如果想訪問外部類的成員,則可以使用 (外部類名.成員)
            System.out.println(name + " 外部類name= " + Outer10.name);
            cry();
        }
    }

    public void m1() { //外部類---訪問------>靜態內部類 訪問方式:建立物件,再訪問
        Inner10 inner10 = new Inner10();
        inner10.say();
    }

    public Inner10 getInner10() {
        return new Inner10();
    }

    public static Inner10 getInner10_() {
        return new Inner10();
    }
}


課堂測試題

判斷輸出:

package com.hspedu.innerclass;

public class InnerClassExercise {
    public static void main(String[] args) {

    }
}

class Test {//外部類

    public Test() {//構造器
        Inner s1 = new Inner();
        s1.a = 10;
        Inner s2 = new Inner();
        System.out.println(s2.a);
    }

    class Inner { //內部類,成員內部類
        public int a = 5;
    }

    public static void main(String[] args) {
        Test t = new Test();
        Inner r = t.new Inner();//5
        System.out.println(r.a);//5
    }
}