?你不知道的Java內部類

六脈神劍發表於2019-12-20

前言

文字已收錄至我的GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習群,群聊號碼:549684836 鼓勵大家在技術的路上寫部落格

絮叨

看了很多原始碼,都有用到內部類,但是自己以前在生產環境上,用的確實少,也有用過但是很少,所以今天就打算好好的把它從頭到尾的過一遍。
可能我寫文章有點亂,但是我是發現自己少了啥,我就補啥,如果是寫系列的話,就肯定是從頭到尾的

定義

可以將一個類的定義放在裡另一個類的內部,這就是內部類,所謂的內部類的概念只是出現在編譯階段,對於jvm層是沒有內部類這個概念的。我們可以利用內部類來解決

  • 類的單繼承問題,外部類不能再繼承的類可以交給內部類繼承
  • 我們可以通過定義內部類來實現一個類私屬於一個類,實現更好的封裝性
  • 程式碼優化:它需要更少的程式碼

分類

內部類可以分為:

  • 靜態內部類。
  • 非靜態內部類。

非靜態內部類又可以分為:

  • 成員內部類。
  • 方法內部類。
  • 匿名內部類。

靜態內部類

我感覺這個是用的最多的,你比如說Redis的key的設計, 因為我們要中間拼接:號,所以用靜態內部類去組成不同的key是非常好的,這樣可以讓相同型別的key再同一個檔案目錄下

?你不知道的Java內部類

靜態內部類的定義和普通的靜態變數或者靜態方法的定義方法是一樣的,使用static關鍵字,只不過這次static是修飾在class上的,一般而言,只有靜態內部類才允許使用static關鍵字修飾,普通類的定義是不能用static關鍵字修飾的,這一點需要注意一下。下面定義一個靜態內部類:

public class Out {
    private static String name;
    private int age;

    public static class In{
        private int age;
        public void sayHello(){
            
            System.out.println("my name is : "+name);
            //--編譯報錯---
            //System.out.println("my age is :"+ age);
        }
    }
}
複製程式碼

在上述程式碼中,In這個類就是一個靜態內部類。我們說內部類是可以訪問外部類的私有欄位和私有方法的,對於靜態內部類,它遵循一致的原則,只能訪問外部類的靜態成員。上述程式碼中,外部類的非靜態私有欄位age在靜態內部類中使不允許訪問的,而靜態欄位name則是可訪問的。下面我們看,如何建立一個靜態內部類的例項物件。

public static void main(String [] args){
    Out.In innerClass = new Out.In();
    innerClass.sayHello();
}
複製程式碼

使用場景,一般來說,對於和外部類聯絡緊密但是並不依賴於外部類例項的情況下,可以考慮定義成靜態內部類。下面我們看稍顯複雜的成員內部類。

成員內部類

我們說了,四種不同型別的內部類都各自有各自的使用場景,靜態內部類適合於那種和外部類關係密切但是並不依賴外部類例項的情況。但是對於需要和外部類例項相關聯的情況下,可以選擇將內部類定義成成員內部類。以下程式碼定義了一個簡單的成員內部類:

public class Out {
    private String name;

    public void showName(){
        System.out.println("my name is : "+name);
    }

    public class In{
        public void sayHello(){
            System.out.println(name);
            Out.this.showName();
        }
    }
}
複製程式碼

以上定義了一個簡單的內部類In,我們的成員內部類可以直接訪問外部類的成員欄位和成員方法,因為它是關聯著一個外部類例項的。下面我們看看在外部是如何建立該內部類例項的。

public static void main(String [] args){
    Out out = new Out();
    out.setName("六脈神劍")
    Out.In in = out.new In();
    in.sayHello();
}
複製程式碼

因為成員內部類是關聯著一個具體的外部類例項的,所以它的例項建立必然是由外部類例項來建立的。對於例項的建立,我們只需要記住即可,成員內部類的例項建立需要關聯外部類例項物件,靜態內部類例項建立相對簡單。下面我們主要看看在編譯階段編譯器是如何保持內部類對外部類成員資訊可訪問的。

使用場景,對於那種要高度依賴外部類例項的情況下,定義一個成員內部類則會顯的更加明智。

方法內部類

方法內部類,顧名思義,定義在一個方法內部的類。方法內部類相對而言要複雜一些,下面定義一個方法內部類:

public class Out {
    private String name;

    public void sayHello(){
        class In{
            public void showName(){
                System.out.println("my name is : "+name);
            }
        }

        In in = new In();
        in.showName();
    }
}
複製程式碼

我們定義了一個類,在該類中又定義了一個方法sayHello,然而在該方法中我們定義了一個內部類,類In就是一個方法內部類。我們的方法內部類的生命週期不超過包含它的方法的生命週期,也就是說,方法內部類只能在方法中使用。所以在宣告的時候,任何的訪問修飾符都是沒有意義的,於是Java乾脆不允許使用任何的訪問修飾符修飾方法內部類。其中還需要注意一點的是,定義和使用時兩回事,別看那一大串定義類的程式碼,你實際想要使用該類,就必須new物件,而對於方法內部類而言,只能在方法內部new物件。這就是方法內部類的簡單介紹,下面我們看看其實現原理。

有關方法內部類的實現原理其實是和成員內部類差不太多的,也是在內部類初始化的時候為其傳入一個外部類例項,區別在哪呢?就在於方法內部類是定義在具體方法的內部的,所以該類除了可以通過傳入的外部例項訪問外部類中的欄位和方法,對於包含它的方法中被傳入的引數也會隨著外部類例項一起初始化給內部類。

毋庸置疑的是,方法內部類的封裝性比之前介紹的兩種都要完善。所以一般只有在需要高度封裝的時候才會將類定義成方法內部類。

匿名內部類

可能內部類的所有分類中,匿名內部類的名號是最大的,也是我們最常用到的,多見於函數語言程式設計,lambda表示式等。下面我們重點看看這個匿名內部類。

匿名內部類就是沒有名字的內部類,在定義完成同時,例項也建立好了,常常和new關鍵字緊密結合。當然,它也不侷限於類,也可以是介面 ,可以出現在任何位置。下面我們定義一個匿名內部類:

如果您必須重寫類或介面的方法,則應該使用它。可以通過兩種方式建立Java匿名內部類

//首先定義一個普通類
public class Out {
    private String name;

    public void sayHello(){
        System.out.println("my name is :" + name);
    }
}
複製程式碼
//定義和使用一個匿名內部類
public static void main(String [] args){
    Out out = new Out(){
        @Override
        public void sayHello(){
            System.out.println("my name is cyy");
        }
        public void showName(){
            System.out.println("hello single");
        }
    };
    out.sayHello();
}
複製程式碼

從上述程式碼中可以很顯然的讓我們看出來,我們的匿名內部類必定是要依託一個父類的,因為它是沒有名字的,無法用一個具體的型別來表示。所以匿名內部類往往都是通過繼承一個父類,重寫或者重新宣告一些成員來實現一個匿名內部類的定義。實際上還是利用了裡式轉換原理。

其實在看了上述三種內部類的原理之後,反而覺得匿名內部類的實現較為簡單了。主要思路還是將內部類抽離出來,通過初始化傳入外部類的例項以達到對外部類所有成員的訪問。只是在匿名內部類中,被依託的父類不是他的外部類。匿名內部類的主要特點在於,沒有名字,物件只能被使用一次,可以出現在任意位置。所以它的使用場景也是呼之欲出,對於一些對程式碼簡潔度有所要求的情況下,可首選匿名內部類。

總結

以上完成了對四種內部類的簡單介紹,對於他們各自實現的原理也都已經介紹過了。其實大致相同,由於jvm對每個類都要求一個單獨的原始碼檔案,所以編譯階段就完成了分離的操作,但是在分離的過程中又要保持內部類和外部類之間的這種聯絡,於是編譯器新增了一些介面保持這種資訊共享的結構。使用內部類可以大大增加程式的封裝性,使得程式碼整體簡潔度較高。

講完這個後面的函式式介面 引用就好講一點了

結尾

內部類就講那麼多,希望大家以後看原始碼會輕鬆點,哈哈

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !

相關文章