《Java編程思想》筆記10.內部類


點擊進入個人博客

能夠把一個類的定義放在另外一個類的定義內部,這就是內部類。
Java最晦澀的部分之一。
內部類看起來就像是一種代碼隱藏機制,將類只與其餘類的內部。但遠不止如此,內部類瞭解外部類,並能與之通訊。shell

10.1 建立內部類

建立內部類的方式就如同你想的同樣——把類的定義置於外圍類的裏邊設計模式

10.2 連接到外部類

  • 當生成一個內部類的對象時,此對象與製造它的外部對象之間就有了一種聯繫,因此它能訪問其外圍對象的全部成員。
  • 內部類還擁有其外圍類的全部元素的訪問權。當某個外部類當對象建立了一個內部類對象時,此內部類對象一定會祕密地捕獲一個指向那個外部類對象當引用。當你訪問外部類成員時,就是用那個引用來選擇外部類當成員。

10.3 使用.this與.new

  • 若是你須要生成對外類對象的引用,可使用外部類.this。這樣產生的引用自動地具備正確類型,這一點在編譯期就被知曉並收到檢查,所以沒有任何運行開銷。
  • 經過外部類的對象建立內部類(非static內部類)對象,能夠經過外部類.new建立內部類
public class Outer {
    void func() {
        System.out.println("Test");
    }

    class Inner {
        void func() {
            System.out.println("Inner");
            // .this語法
            Outer.this.func();
        }
    }

    public static void main(String[] args) {
        // .new語法
        new Outer().new Inner().func();
    }
}

10.4 內部類與向上轉型

內部類的優勢:能夠更好的隱藏細節
特色:安全

  1. 外部類能夠訪問內部類的全部元素,不管什麼修飾符。
  2. 普通內部類內不能有static域和方法。
  3. 一個內部類能夠被嵌套多層,並且能夠訪問全部外圍類的成員。

10.5 局部內部類

能夠在一個方法或任意做用域內定義內部類,成爲局部內部類。這麼作的理由:閉包

  1. 你實現了某類型的接口,因而能夠建立並返回對其的引用
  2. 你要解決一個複雜的問題,想建立一個類來複制你的解決方案,但又不但願這個類是公共可用的。
public class Outer {

    public void func() {
        // 方法內部的內部類
        class InnerMethod {
            void func() {
                System.out.println("class in method");
            }
        }
        new InnerMethod().func();
    }

    public void f() {
        if(true) {
            // 做用域內部的內部類
            class InnerScope {
                void func() {
                    System.out.println("class in scope");
                }
            }
            new InnerScope().func();
        }
    }

    public static void main(String[] args) {
        new Outer().func();
        new Outer().f();
    }
}
特色:
  1. 局部內部類相似方法的局部變量,因此在類外或者類的其餘方法中不能訪問這個內部類。但這並不表明局部內部類的實例和定義了它的方法中的局部變量具備相同的生命週期。
  2. 能夠在同一個子目錄(包)下起一個跟局部內部類相同的類,不會有衝突。
  3. InnerScope類被嵌套到if語句中,這並非說該類到建立是有條件的,他跟其餘的類同樣被編譯過了。
  4. 由於不存在外部可見性,局部內部類不能用權限修飾符。
  5. 不能在局部內部類中使用可變的局部變量,可使用final的局部變量。
  6. 能夠訪問外圍類的成員變量。若是是static方法,則只能訪問static修飾的成員變量。
  7. 可使用finalabstract修飾。

10.6 匿名內部類

簡介:
  • inner()方法將返回值的生成與表示這個返回值的類定義結合在一塊兒,並且這個類沒有名字。
  • 建立一個繼承某個類(或者實現某個接口)的匿名類對象。
public class Outer {

    private final String outerStr = "Outer";

    class Inner {
        public Inner(String str) {
            System.out.println("Inner Constructor " + str);
        }
        public void func() {
            System.out.println("Inner");
        }
    }

    public Inner inner() {
        return new Inner("Dota") {
            {
                // 跟構造方法同樣初始化
                str3 = "LOL";
            }
            private String str1 = Outer.this.outerStr;
            private String str2 = outerStr;
            private String str3;
            @Override
            public void func() {
                System.out.println(str1);
                System.out.println(str2);
                System.out.println(str3);
            }
        };
    }

    public static void main(String[] args) {
        new Outer().inner().func();
    }
}
細節:
  1. 返回的類型被自動向上轉型成Inner的引用。
  2. 若是構造方法帶參數,也能夠在new Inner()中傳遞參數給基類的構造器。
  3. 在匿名內部類定義字段時,能夠初始化。
  4. 在匿名內部類使用外部類的對象時,只能使用final的。
  5. str1str2是同樣的
  6. 匿名內部類沒有名字,也就沒有構造器,可是能夠經過實例初始化模擬構造器。可是你不能重載實例初始化方法,因此只能有一個這樣的構造器。
  7. 變量str不要求是final的,由於str是傳遞給基類的構造器的,匿名內部類沒法使用。
  8. 匿名內部類能夠繼承類或者實現接口,但不能二者兼得。

10.6.1 再訪工廠方法

代碼更加簡潔框架

10.7 嵌套類(靜態內部類)

嵌套類:

若是不須要內部類對象與其外圍對象之間有聯繫,那麼能夠將內部類聲明爲staticide

特色:
  1. 普通內部類對象隱式的保存了外部類對象,但嵌套類並不是如此。
  2. 要建立嵌套類的對象,並不須要外部類的對象
  3. 不能從嵌套類對象中訪問外部類的非靜態對象。
  4. 普通內部類不能有static域和static方法,但嵌套類能夠有。

10.7.1 接口內部的類

  • 嵌套類能夠做爲接口的一部分,還能夠實現其外部接口。
  • 若是你想建立某些公共代碼,使得它們能夠被某個接口的全部不一樣實現所共用,那麼使用接口內部的嵌套類會很方便。
  • 可使用嵌套類的main方法來實現調試。

10.7.2 多層嵌套

  • 一個內部類能夠嵌套多層
  • 一個嵌套類也能夠被嵌套多層。

10.8 爲何須要內部類

  1. 外部類能夠有多個內部類,每一個內部類都能獨立的繼承自一個(接口的)實現,因此不管外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。
  2. 接口解決類部分「多重繼承」,內部類補充的實現了「多重繼承。
  3. 內部類能夠有多個實例,每一個實例都有本身的狀態信息,而且與外圍類對象的信息相互獨立。
  4. 再單個外圍類中,可讓多個內部類以不一樣的方式實現同一個接口。
  5. 建立內部類對象的時候並不必定依賴外部類對象的建立。
  6. 內部類並無使人迷惑的「is-a」關係,他就是一個獨立的實體。

10.8.1 閉包與回調

閉包
  1. 閉包(closure)是一個可調用的對象,它記錄了一些信息,這些信息來自於建立它的做用域。
  2. 經過上述定義,能夠看出內部類就是面向對象的閉包,由於它不只包含外圍類對象(建立內部類的做用域)的信息,還自動擁有一個指向此外圍類對象的引用,在此做用域內,內部類有權操做全部成員。
  3. 經過內部類實現閉包的功能是優良的解決方案,它比指針更靈活、更安全。
回調

回調函數的定義:在計算機程序設計中,回調函數是指經過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用。這一設計容許了底層代碼調用在高層定義的子程序。
非回調函數的場景:一個程序B有一個方法b(),要調用程序A中的另外一個方法a()。這個很簡單,只須要在程序B的方法b()new A().a()就能夠了。
回調函數:跟上述同樣,可是程序A中的方法a()在完成任務後,還會調用一個預約義好的回調函數B在方法b()中,能夠按照預約義好的回調函數接口實現相關邏輯,而後把這段邏輯傳遞給A,這樣在B.b()調用A.a()的時候,就會執行這段邏輯。函數

// A定義好的回調接口
interface Callback {
    void callback();
}

// A定義
public class A {
    Callback callback;

    public A(Callback callback) {
        this.callback = callback;
    }

    public void a() {
        System.out.println("a");
        callback.callback();
    }
}

class B {

    public static void main(String[] args) {
        A a = new A(new Callback() {
            @Override
            public void callback() {
                System.out.println("callback");
            }
        });
        a.a();
    }
}
// Output:
a
callback

10.8.2 內部類與控制框架

應用程序框架
  1. 應用程序框架就是被設計用來解決某類特定問題的一個或者一組類。
  2. 要運用某個應用程序框架,一般是繼承一個或多個類,並覆蓋某些方法。在覆蓋後的方法中,編寫代碼定製應用程序框架提供的通用解決方案(這是模板方法的一個例子)。
控制框架

控制框架是一類特殊的應用程序框架,他用來解決響應事件的需求。主要用來響應事件的系統被稱爲事件驅動系統。this

public class Test {
    private boolean light;
    private boolean water;

    class LightEvent extends SwitchEvent {
        @Override
        public void on() {
            light = true;
        }

        @Override
        public void off() {
            light = false;
        }
    }

    class WaterEvent extends SwitchEvent {
        @Override
        public void on() {
            water = true;
        }

        @Override
        public void off() {
            water = false;
        }
    }
}

abstract class SwitchEvent {
    public abstract void on();
    public abstract void off();
}

上述代碼描述了一個開關事件的抽象類,和兩個繼承該抽象類的內部類。這些內部類可以自由地訪問Test類中的字段,無需任何條件。設計

命令設計模式

記得看!!!指針

10.9 內部類的繼承

public class Test extends Outer.Inner {
    // 若是沒有下面的構造方法會編譯失敗
    public Test(Outer outer) {
        outer.super();
    }
}

class Outer {
    class Inner {}
}
  • 能夠看到Test只繼承了內部類Inner,而不是外部類。
  • 當要生成一個構造器時,必需要增長這樣一段代碼outer.super();
  • 解釋:內部類的構造器必須鏈接到指向外部類對象的引用,而在內部類的子類中再也不存在可鏈接的默認對象。因此須要在子類的構造器中包含指向外部類的引用,必須是帶參數的,並且參數類型是外部類。說白了就是,內部類的對象依賴外部類的對象,內部類的子類的對象,也仍舊是依賴外部類的對象的。

10.10 內部類能夠被覆蓋嗎

public class Test extends Outer {
    class Inner {}
}

class Outer {
    class Inner {}
}

上述代碼中:Test繼承了Outer並「覆蓋」了Inner,但這沒有用;這兩個Inner是徹底絕不相干但兩個類,各自活在各自的命名空間裏。

public class Test extends Outer {
    class Inner extends Outer.Inner {
        @Override
        void func() {
            System.out.println("Test.Inner.func()");
        }
    }

    public Test() {
        setInner(new Inner());
    }

    public static void main(String[] args) {
        new Test().getInner().func();
    }
}

class Outer {
    private Inner inner;
    class Inner {
        void func() {
            System.out.println("Outer.Inner.func()");
        }
    }

    public Inner getInner() {
        return inner;
    }

    public void setInner(Inner inner) {
        this.inner = inner;
    }
}

上述代碼中:Test繼承了OuterTest.Inner繼承了Outer.Inner。此時若是覆蓋Inner中的方法,當構造器調用setInner(new Inner());的時候,是把Test.Inner向上轉型爲Outer中的引用inner

10.11 局部內部類

(見10.5)
前面提到過,能夠在代碼塊裏建立內部類,典型的方式是在方法體內。
局部內部類不能有訪問說明符,由於他不是外部類的一部分。
局部內部類能夠訪問當前代碼塊內的常量,以及此外圍類的成員。

局部內部類OR匿名內部類
  1. 局部內部類能夠有構造器以及重載構造器,而匿名內部類只能用於實例初始化。
  2. 局部內部類能夠建立多個對象,而匿名內部類最多有一個

10.12 內部類標識符

每一個類都會產生一個.class文件,其中包含了如何建立該類的對象的所有信息(此信息產生一個「meta-class」,叫作Class;對象),內部類也是如此。

類文件的命名規則
  1. 外部類的名字:外部類名.class
  2. 普通內部類:外部類名$內部類名.class
  3. 匿名內部類:外部類名$編譯器分配的數字.class
  4. 多層嵌套:按從外到內用$分割.class
小問題

對於Unix shell而言,$是一個元字符,因此在列出.class文件的時候,有時會有問題。

10.13 總結

內部類涉及內容相對複雜,多花點時間吧~

相關文章
相關標籤/搜索