還好面試官還沒問,趕緊把【內部類】的知識點補上

1. 內部類的含義

知道內部類這個概念,除了在用鏈表時定義節點類時,其餘情況具體怎麼使用感覺很生疏。再次回顧到這個知識點了,做一個系統的總結

內部類,從字面意思上理解爲 “定義在類內部的類”。可以把它理解爲汽車的發動機,只能在汽車的內部使用,給火車它就用不了了;人體的心臟,在人體裏面維持着血液循環,拿出來人就涼了。這些 “內部的部件”,是隻能夠依賴於外部而使用的,我們稱這種類爲內部類。

內部類是依賴於外部類而存在的,一個類是沒法談內外之說的。

2. 內部類簡介

內用外,隨意訪問;外用內,需要內部類對象。

內部類一個特點就是能夠訪問外部類所有字段(即包括了 private 權限的)。同時,外部類也可以訪問內部類的所有訪問權限修飾的字段。

就拿人體來說,你能夠很明顯的感受到你的心臟❤還不是還在跳,心臟也知道你是否還活着。人沒了,心臟也就休息了;心臟跳不動了,人也涼了。


內部類的共性

(1). 內部類雖然在類的內部定義,但是是一個獨立的類,在編譯之後內部類會被編譯成獨立的.class文件,但是前面冠以外部類的類名和$符號 。

(2). 內部類也需要實例化才能訪問

(3). 內部類聲明成靜態的,就不能隨便訪問外部類的成員變量了,此時內部類只能訪問外部類的靜態成員變量 。

(4). 外部類不能直接訪問內部類的的成員,但可以通過內部類對象來訪問。內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變量,無論是否是private的。(後面有解釋,詳細的源碼編譯解釋參考文末鏈接文章)

3. 內部類的分類

I. 成員內部類

就是在一個類的內部再定義一個類,成員內部類不能含有static的變量和方法。

如何使用成員內部類,有兩種方式:

直接訪問

外部類名稱.內部類名稱 對象名 = new 外部類名稱().new 內部類名稱();

直接創建內部類對象來訪問內部類

public class BodyII {
    private String name;

    //成員內部類
    public class Heart {
       //內部類方法
       public void beat() {
           System.out.println("內部類方法");
       }
    }

    //外部類的方法
    public void breathe() {
        new Heart().beat();
        System.out.println("外部類方法");
    }

    public static void main(String[] args) {
        //直接創建內部類對象來訪問
        //外部類名稱.內部類名稱 對象名 = new 外部類名稱().new 內部類名稱();
        BodyII.Heart body = new BodyII().new Heart();
        body.beat();
    }
}

內部類方法

間接訪問

在外部類的方法當中,使用內部類;藉助外部類的方法間接調用內部類方法

public class BodyI {
    private String name;

    //成員內部類
    public class Heart {
       //內部類方法
       public void beat() {
           System.out.println("內部類方法");
       }
    }

    //外部類的方法
    public void breathe() {
        new Heart().beat();
        System.out.println("外部類方法");
    }

    public static void main(String[] args) {
        BodyI body = new BodyI();
        //通過外部類的對象,調用外部類的方法,裏面間接在使用內部類 Heart
        body.breathe();
    }
}

內部類方法

外部類方法

內部類重名變量的訪問

在有內部類的類中,成員變量重名可能會出現

  • 外部類的成員變量

  • 內部類的成員變量

  • 局部變量

在內部類中,訪問外部類的重名成員變量時:

外部類名稱.this.外部類成員變量名

public class Outer {
     //外部類名稱.this.外部類成員變量名
     int num = 10;
     public class Inner {
         int num = 20;
         public void innerMethod() {
             int num = 30;
             System.out.println(num); //局部變量,就近原則
             System.out.println(this.num);  //內部類的成員變量
             System.out.println(Outer.this.num);  //外部類的成員變量
         }
     }

    public static void main(String[] args) {
        Outer.Inner obj = new Outer().new Inner();
        obj.innerMethod();
    }
}

運行結果

30
20
10


II. 靜態內部類

我們知道,一個類的靜態成員獨立於這個類的任何一個對象存在,只要有訪問權限,我們就可以通過 類名.靜態成員名 來訪問這個靜態成員,不需要通過實例化對象來調用。

同樣的,靜態內部類也是作爲一個外部類的靜態成員而存在,創建一個類的靜態內部類對象不需要依賴其外部類對象

先來看個簡單的例子:

運行結果

訪問外部類靜態成員變量 Hello World

【內部類代碼總結】

class Out {

    private static int i = 10;
    private int j = 20;

    public static void outer_f1() {
        System.out.println("訪問外部類的靜態成員");
    }

    public void outer_f2() {
        System.err.println("不能訪問外部類的非靜態成員");
    }

    //靜態內部類
    private static class Inner {

        static int inner_i = 100;
        int inner_j = 200;

        // 靜態內部類只能訪問外部類的靜態成員(包括靜態變量和靜態方法)
        static void inner_f1() {
            System.out.println("Outer.i:  " + i);
            outer_f1();
        }

        void inner_f2() {
            // 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變量和非靜態方法)
        }
    }

    public void outer_f3() {
        // 外部類訪問內部類的靜態成員:內部類.靜態成員
        System.out.println("Inner.i:  " + Inner.inner_i);
        Inner.inner_f1();

        // 外部類訪問內部類的非靜態成員:實例化內部類即可
        new Inner().inner_f2();
    }
}

public class Outerclass {
    public static void main(String[] args) {
        new Out().outer_f3();
    }
}

運行結果

Inner.i: 100
Outer.i: 10
訪問外部類的靜態成員

可以看到,我們可以把靜態內部類當做外部類的一個靜態成員,在創建其對象無需依賴外部類對象(訪問一個類的靜態成員無需依賴這個類的對象,因爲它是獨立於所有類的對象的)

但是,靜態內部類中也無法訪問外部類的非靜態成員,因爲外部類的非靜態成員是屬於每一個外部類對象的,而本身靜態內部類就是獨立外部類對象存在的,所以靜態內部類不能訪問外部類的非靜態成員,而外部類依然可以訪問靜態內部類對象的所有訪問權限的成員,這一點和普通內部類無異。

總結

  • 靜態內部類只能訪問外部類靜態成員,不能訪問普通成員

  • 外部類可以訪問內部類的靜態成員:內部類.靜態成員

  • 外部類訪問內部類的非靜態成員:實例化內部類即可


III. 局部內部類

局部內部類,可以類比局部方法、局部變量,都是定義在方法中的,那麼局部內部類也就是定義在方法中了。

如果一個類是定義在一個方法內部的,那麼這就是一個局部內部類。

局部的含義:只有當前所屬的方法才能使用它,出了這個方法外面就不能用了。

public class Outer {
    public void methodOuter() {
        class Inner {  //局部內部類
            int num = 10;
            public void methodInner() {
                System.out.println("調用局部內部類:" + num);
            }
        }
        Inner in = new Inner();
        in.methodInner();
    }

    public static void main(String[] args) {
        Outer out = new Outer();
        out.methodOuter();
    }
}

運行結果:

調用局部內部類:10

內部類的權限修飾符

權限修飾符:public > protected > (default) > private

定義內部類時,權限修飾符規則:

  1. 外部類:public / (default)

  2. 成員內部類:public > protected > (default) > private

  3. 局部內部類:(default)默認不寫即可

局部內部類的final問題

局部內部類,如果希望訪問所在方法的局部變量,那麼這個局部變量必須是【有效 final的

什麼意思內呢爲什麼成員變量num必須得被final修飾呢?

答:因爲生命週期的問題,要保證在成員變量num銷燬之後,內部類的對象依然可以使用num,需要將num從棧內存拷貝一份到常量池中。

  1. new出來的對象在堆內存當中

  2. 局部變量是跟着方法走的,在棧內存當中

  3. 方法運行結束之後,立刻出棧,局部變量就會立刻消失

  4. 但是new出來的對象會在堆當中持續存在,直到垃圾回收消失


IV. 匿名內部類

如果接囗的實現類(或者是子類)只需要使用唯一的一次,那麼這種情況下就可以省略掉該類的定義,而改爲使用【匿名內部類

匿名內部類定義:

對格式new 接口名稱(){ };進行解析:

  1. new代表創建對象的動作

  2. 接口名稱就是匿名內部類需要實現的接口

  3. { };這纔是匿名內部類的內容

匿名內部類,其實就是沒有類名的局部內部類

匿名內部類的使用

匿名內部類的使用是很頻繁的,最常用的就是在接口的實現上。

通常情況下,我們在再實用接口時,會先定義接口,然後是實現接口,最後是實例化實現類來使用。

比如說一個登陸功能,我們通常會這樣寫:

public interface Userdao {
    void login(User user); //用戶登錄
}

public class UserdaoImpl implements UserDao {
    public void login(User user); {
        System.out.println("查詢用戶記錄")}
}

......


Userdao dao = new UserDaoImpl();
dao.login();

像這樣寫每個功能模塊是獨立的,代碼的邏輯結構更加清晰,方便我們多次使用接口和實現類。

但是,有的時候我們的實現類只會使用一次,像上面定義又比較繁瑣,必須得有接口的實現類纔可以實例化來使用,這時我們可以使用匿名內部類。

【舉例來說】

在我們定義實現鏈表時,我們會寫一個鏈表的迭代器來遍歷鏈表的每一個節點,就要實現Iterable接口

但是,我們對這個類只使用一次,最終能夠迭代遍歷鏈表即可,所以用匿名內部類得方式實現Iterable接口。

public class LinkList<T> implements Iterable<T> {

    ......
    //鏈表功能實現

    /**
     * 實例化Iterator對象,Iterator是接口,用內部類來實例化
     * @return
     */
    @Override
    public Iterator iterator() {

        return new Iterator() {

            private Node node = head;

            @Override
            public boolean hasNext() {
                return node.next != null;
            }

            @Override
            public Object next() {
                //指向第一個存儲數據的節點
                node = node.next;
                return node.data;
            }
        };
    }
}

那匿名內部類好像寫的很隱祕,不太容易看出來。其實,鑑別的方法很簡單,如果代碼塊有;,那就要好好判斷一下了。

{ };

匿名內部類小結

匿名對象:

匿名內部類實現了方法. 匿名對象來調用方法!

  1. 匿名內部類,在【創建對象】的時候,只能使用唯一一次。

    • 如果希望多次創建對象,而且類的內容一樣的話,那麼就必須使用單獨定義的實現類了
  2. 匿名對象,在【調用方法】的時候,只能調用唯一一次。

    • 如果希望同一個對象,調用多次方法,那麼必須給對象起個名字。
  3. 匿名內部類是省略了【實現類/子類名稱】,但是匿名對象是省略了【對象名稱】

    • 強調:匿名內部類和匿名對象不是一回事!!!

Q:爲什麼內部類可以訪問外部類的成員

深入理解Java中爲什麼內部類可以訪問外部類的成員

  1. 編譯器自動爲內部類添加一個成員變量, 這個成員變量的類型和外部類的類型相同, 這個成員變量就是指向外部類對象的引用;

  2. 編譯器自動爲內部類的構造方法添加一個參數, 參數的類型是外部類的類型, 在構造方法內部使用這個參數爲添加的成員變量賦值;

  3. 在調用內部類的構造函數初始化內部類對象時, 會默認傳入外部類的引用

Q:爲什麼成員內部類不能含有static的變量和方法?

其實還是類與對象的區別,靜態屬性不依賴於對象,所以訪問修改的時候不需要依賴當前有沒有存活的對象。

內部類可以認爲是外部類的一個成員變量,想要訪問內部類,必須先實例化外部類,然後通過外部類才能訪問內部類。只要是成員變量,就需要依賴具體的對象,這個時候如果在非靜態內部類裏面聲明靜態屬性就會破壞了這一邏輯,所以java語言在語義層面不允許我們那麼做。

我這裏只是做個基礎的介紹,深入的去探究源碼,編譯文件來說明自己有些吃力,我會繼續探究逐步深入完善文章,請大家參考文中的鏈接文章來學習。


【參考文章】

  1. 詳解 Java 內部類

  2. 深入理解Java:內部類

  3. 帶你看懂Java內部類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章