【java基礎整理】2-內部類

1.內部類的定義和性質

??內部類,顧名思義,就是在一個類里面定義一個類,但是內部類的一個特殊之處在于,它能夠獲取到其外部類對象的所有成員,包括private字段
??這時因為當外部類對象在創建一個內部類對象的時候,編譯器又開始悄悄咪咪的把外部類對象的引用傳入給內部類對象,于是內部類就可以通過這個外部類對象的引用對外部類的所有成員進行訪問。在內部類中,可以通過外部類類名.this的方式獲取到外部類對象的引用,見下面的例子。
??基于這個原因,在創建內部類對象的時候,不能使用外部類類名進行創建,而必須使用外部類的對象來進行創建。下面是栗子:

public class Outer{
    public class Inner{
        public Outer getOuter(){
            //可以在內部類中通過外部類類名.this獲取到外部類的引用
            return Outer.this;   
        }
    }

    public static void main(string[] args){
        //編譯不通過,不能通過外部類類名創建內部類,因為Inner非靜態
        //Inner inner = new  Outer.Inner(); 

        //編譯通過,通過外部類對象創建內部類
        Outer outer = new Outer();
        Inner inner = outer.new Inner();     
    }
}

??這個時候你是不是隱約感覺到了什么不對!在java基礎整理1當中我們提過,static關鍵字可以使類的成員不再與單個具體對象相互關聯,而是與類本身關聯,那么我們如果用static關鍵字對內部類進行修飾會怎么樣呢?答案是在創建一個static的內部類時,你并不需要一個外部類對象,這時我們稱這個內部類為嵌套類(或靜態內部類),下文中static內部類我將一律以嵌套類(或靜態內部類)對其進行稱呼,而內部類則指代非static的內部類

2.匿名內部類

??在匿名內部類之前,我們首先了解一下在方法和作用域中的內部類

??在方法中定義的內部類其實很好理解,即在方法體當中,內部類可以訪問,如下

//在方法中定義的內部類
public class InnerClassInMethod {
    public interface Destination{
        String readLabel();
    }

    //一個返回Destination的方法
    public Destination methodA(String s){
        //在其中定義一個內部類, 該內部類僅可在methodA(String s)的方法體中訪問
        class InnerDestination implements Destination{
            private String label;
            private InnerDestination(String dest){
                label  = dest;
            }
            public String readLabel(){
                return label;
            }
        }
        return new InnerDestination(s);
    }
}

??在作用域中定義的內部類與在方法中定義類似,在定義內部類的整個作用于當中,內部類都是可以訪問的

//在作用域中定義的內部類
public class InnerClassInField {
    public interface Destination{
        String readLabel();
    }

    //一個返回Destination的方法
    public void methodB(Boolean b){
        if(b){
            //在其中定義一個內部類, 該內部類僅可在methodA(String s)的方法體中訪問
            class InnerDestination implements Destination{
                private String label;
                private InnerDestination(String whereTo){
                    label  = whereTo;
                }
                public String readLabel(){
                    return label;
                }
            }
            //可以在if()的作用域中對內部類進行調用
            InnerDestination innerDestination = new InnerDestination("在作用域中可以調用");
        }
        
        //在這里就不能對內部類進行調用
        //InnerDestination innerDestination = new InnerDestination("在作用域外則不能調用");
        
    }
}

匿名內部類則是以上情況的簡化形式,當匿名內部類需要使用一個從外部定義的對象,那么這個對象必須聲明為final的,否則會出現編譯時錯誤消息,下面我們將InnerClassInMethod.java改變為匿名內部類的

//InnerClassInMethod.java的匿名內部類形式
public class AnonymousClassInMethod {
    public interface Destination{
        String readLabel();
    }

    //一個返回Destination的方法
    public Destination methodA(final String s){
        //使用匿名內部類返回Destination對象,因為需要使用外部String對象s,因此s被定義為final
        return new Destination() {
            private String label = s;
            @Override
            public String readLabel() {
                return label;
            }
        };
    }
}

3.嵌套類(靜態內部類)

??前面我們在第1部分中最后有提到,在創建內部類時,外部類對象會將自己的引用傳給內部類持有。但是當我們用static關鍵字修飾內部類時,內部類對象就不會與外部類的對象相聯系,即不再持有外部類對象的引用,這時該內部類我們稱之為嵌套類
??嵌套類有幾個性質:

  • 創建嵌套類時不需要外部類的對象
  • 嵌套類不能訪問外部類的非靜態對象
  • 嵌套類可以包含static數據和字段,而普通的內部類不能擁有這些

因為嵌套類不具有對外部類對象的引用,以上性質就比較好理解了

4.為什么要用內部類(重點!!!)

??由于java的繼承機制使得子類只能具有一個父類,這個限制會使得一些編程問題變得難以解決
??但是內部類的存在使得這個問題變得容易起來。首先我們思考一下內部類的特點,往往在我們的編程當中,外部類可以繼承某個類或某個接口,內部類也可以獨立的繼承某個類或某個接口,而同時由于內部類能夠訪問其外部類的對象,因此內部類能夠訪問自己所繼承的以及其外部類對象所繼承的所有方法,于是該內部類可以看做繼承了兩個類。因此實際上,內部類是一種多重繼承的實現方式

以下介紹兩種內部類的應用場景

a. 回調 + “鉤子”
/** 一個帶有increment()方法的接口*/
interface Incrementable{
    void increment();
}

/** 第一個回調類,實現Incrementable接口 */
class Callee1 implements Incrementable{
    private int i = 0;
    @Override
    public void increment() {
        i++;
        System.out.println(i);
    }
}

/**一個同樣含有increment()但做著不同工作的類*/
class OtherOptionClass{
    public void increment(){
        System.out.println("other operation");
    }
}

/**
 * 第二個回調類
 * 若因為某些原因必須繼承OtherOptionClass,但又想實現Incrementable
 * 由于兩者都具有increment()方法,則可以通過內部類來實現
 */
class Callee2 extends OtherOptionClass{
    private int i = 0;
    /** 繼承并覆蓋OtherOptionClass中的increment方法 */
    @Override
    public void increment(){
        super.increment();
        i++;
        System.out.println(i);
    }

    /** 定義內部類,內部類實現Incrementable接口 */
    private class Closure implements Incrementable{
        @Override
        public void increment() {
            /** 調用外部類的方法 */
            Callee2.this.increment();
        }
    }

    /** 定義一個方法以獲取到Closure對象 */
    Incrementable getCallbackReference(){
        return new Closure();
    }
}

/** 調用者類,需要通過傳入Incrementable對象來構建實例,在適當的時候調用其方法,即回調 */
class Caller{
    Incrementable callBackReference;
    Caller(Incrementable cbr){
        callBackReference = cbr;
    }
    /** 在方法中執行回調 */
    void go(){
        callBackReference.increment();
    }
}

/** 主類 */
public class Callbacks {
    public static void main(String[] args) {
        /** 創建兩個被回調對象 */
        Callee1 c1 = new Callee1();
        Callee2 c2 = new Callee2();
        /** 構建兩個調用者對象,c1傳入自身,c2傳入自己的內部類*/
        Caller caller1 = new Caller(c1);
        Caller caller2 = new Caller(c2.getCallbackReference());
        /** 在方法中執行回調 */
        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
    }
}

/** Output:
1                                  ->caller1.go();
2                                  ->caller1.go();
other operation                    ->caller2.go();
1
other operation                    ->caller2.go();
2
*/

如上例,構建Callee對象,將其作為構造參數傳入Caller對象中,這樣可以讓Caller對象在恰當的時候決定操作Callee類,這就是程序設計時常用到的回調
在這個例子當中,定義了兩個回調類,第二個回調類需要繼承一個類和一個接口,但是它們具有不同作用但具有相同方法標簽的increment()方法,這個時候,我們可以考慮使用內部類對接口進行繼承,并在接口方法實現時調用外部類Callee2的increment()方法,從而達成了外部類Callee2對類和接口的increment()同時進行了繼承
在本例中內部類Closure僅可通過Callee2獲取,通過它可以轉發調用Callee2對象的increment()方法,這被稱為鉤子,另外,在該類的外部獲取到內部類Closure,也僅能通過它調用Callee2對象的increment(),因此這個是個安全的鉤子。

b. 內部類與控制框架

控制框架如控制一個溫室的運行:控制燈、水、溫度的開關,響鈴等操作。

  • 首先可以定義一個模板事件類Event,在這個類中具有一個抽象方法action(); (應用模板模式
  • 然后搭建框架抽象類Controller,該類維護一個Event列表,可以添加刪除Event,并通過run()方法逐條執行Event。
  • 實現具體的框架類GreenHouseControls,具有私有變量light、water、temperate等,并定義多個內部類如LightOn、LightOff等繼承自Event,實現各自的action()方法 (應用命令模式,不同的Event自行決定如果和實現
  • 實現一個main()方法,創建GreenHouseControls實例,操作之~

這個例子雖然也可以不用內部類實現,例如可以單獨定義多個操作類繼承自Event,然后在構造方法中傳入具有light、water、temperate等的GreenHouseControls對象,通過這個對象引用修改其參數。
但是如果使用內部類,就可以將這些所有的細節全部封裝起來,此外內部類能夠很容易訪問外部類GreenHouseControls的變量

該例子詳情請見《java編程思想第四版》p207 - p211

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,737評論 18 399
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,829評論 18 139
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,830評論 0 11
  • 說話是一門藝術活,都說“良言一句三冬暖,惡語傷人六月寒”,話說得好,能夠大事化小,小事化了,干戈化玉帛;話說得不好...
    好聽的暖陽閱讀 205評論 5 6
  • 與人,與事,切不可浮躁,莫總想以己之心,度他人之心?萬事自有規律,眾生難逃蕓蕓之力。 你改變不了什么的話,要么...
    Mark_Per閱讀 213評論 0 0