1. 程式人生 > 實用技巧 >《On Java 8》讀書筆記011_內部類

《On Java 8》讀書筆記011_內部類

https://github.com/LingCoder/OnJava8

內部類

一個定義在另一個類中的類,叫作內部類。

// innerclasses/Parcel2.java
// Returning a reference to an inner class
public class Parcel2 {
    class Contents {
        private int i = 11;

        public int value() { return i; }
    }

    class Destination {
        private String label;

        Destination(String whereTo) {
            label = whereTo;
        }

        String readLabel() { return label; }
    }

    public Destination to(String s) {
        return new Destination(s);
    }

    public Contents contents() {
        return new Contents();
    }

    public void ship(String dest) {
        Contents c = contents();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String[] args) {
        Parcel2 p = new Parcel2();
        p.ship("Tasmania");
        Parcel2 q = new Parcel2();
        // Defining references to inner classes:
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("Borneo");
    }
}

Tasmania

連結外部類

// innerclasses/Sequence.java
// Holds a sequence of Objects
interface Selector {
    boolean end();
    Object current();
    void next();
}
public class Sequence {
    private Object[] items;
    private int next = 0;
    public Sequence(int size) {
        items = new Object[size];
    }
    public void add(Object x) {
        if(next < items.length)
            items[next++] = x;
    }
    private class SequenceSelector implements Selector {
        private int i = 0;
        @Override
        public boolean end() { return i == items.length; }
        @Override
        public Object current() { return items[i]; }
        @Override
        public void next() { if(i < items.length) i++; }
    }
    public Selector selector() {
        return new SequenceSelector();
    }
    public static void main(String[] args) {
        Sequence sequence = new Sequence(10);
        for(int i = 0; i < 10; i++)
            sequence.add(Integer.toString(i));
        Selector selector = sequence.selector();
        while(!selector.end()) {
            System.out.print(selector.current() + " ");
            selector.next();
        }
    }
}

0 1 2 3 4 5 6 7 8 9

Sequence 類只是一個固定大小的 Object 的陣列,以類的形式包裝了起來。可以呼叫 add() 在序列末尾增加新的 Object(只要還有空間),要獲取 Sequence 中的每一個物件,可以使用 Selector 介面。這是“迭代器”設計模式的一個例子,在本書稍後的部分將更多地學習它。Selector 允許你檢查序列是否到末尾了(end()),訪問當前物件(current()),以及移到序列中的下一個物件(next())。因為 Selector 是一個介面,所以別的類可以按它們自己的方式來實現這個介面,並且其他方法能以此介面為引數,來生成更加通用的程式碼。

這裡,SequenceSelector 是提供 Selector 功能的 private 類。可以看到,在 main() 中建立了一個 Sequence,並向其中添加了一些 String 物件。然後通過呼叫 selector() 獲取一個 Selector,並用它在 Sequence 中移動和選擇每一個元素。 最初看到 SequenceSelector,可能會覺得它只不過是另一個內部類罷了。但請仔細觀察它,注意方法 end(),current() 和 next() 都用到了 items,這是一個引用,它並不是 SequenceSelector 的一部分,而是外部類中的一個 private 欄位。然而內部類可以訪問其外部類的方法和欄位,就像自己擁有它們似的,這帶來了很大的方便,就如前面的例子所示。

使用.this和.new

如果你需要生成對外部類物件的引用,可以使用外部類的名字後面緊跟圓點和 this。這樣產生的引用自動地具有正確的型別,這一點在編譯期就被知曉並受到檢查,因此沒有任何執行時開銷。下面的示例展示瞭如何使用 .this:

// innerclasses/DotThis.java
// Accessing the outer-class object
public class DotThis {
    void f() { System.out.println("DotThis.f()"); }

    public class Inner {
        public DotThis outer() {
            return DotThis.this;
            // A plain "this" would be Inner's "this"
        }
    }

    public Inner inner() { return new Inner(); }

    public static void main(String[] args) {
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();
        dti.outer().f();
    }
}

DotThis.f()

有時你可能想要告知某些其他物件,去建立其某個內部類的物件。要實現此目的,你必須在 new 表示式中提供對其他外部類物件的引用,這是需要使用 .new 語法,就像下面這樣:

// innerclasses/DotNew.java
// Creating an inner class directly using .new syntax
public class DotNew {
    public class Inner {}
    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}

要想直接建立內部類的物件,你不能按照你想象的方式,去引用外部類的名字 DotNew,而是必須使用外部類的物件來建立該內部類物件,就像在上面的程式中所看到的那樣。這也解決了內部類名字作用域的問題,因此你不必宣告(實際上你不能宣告)dn.new DotNew.Inner。

在擁有外部類物件之前是不可能建立內部類物件的。這是因為內部類物件會暗暗地連線到建它的外部類物件上。但是,如果你建立的是巢狀類(靜態內部類),那麼它就不需要對外部類物件的引用。

匿名內部類

如果只是簡單地給一個欄位賦值,那麼此例中的方法是很好的。但是,如果想做一些類似構造器的行為,該怎麼辦呢?在匿名類中不可能有命名構造器(因為它根本沒名字!),但通過例項初始化,就能夠達到為匿名內部類建立一個構造器的效果,就像這樣:

// innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class
abstract class Base {
    Base(int i) {
        System.out.println("Base constructor, i = " + i);
    }
    public abstract void f();
}
public class AnonymousConstructor {
    public static Base getBase(int i) {
        return new Base(i) {
            { System.out.println(
                    "Inside instance initializer"); }
            @Override
            public void f() {
                System.out.println("In anonymous f()");
            }
        };
    }
    public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
    }
}

Base constructor, i = 47
Inside instance initializer
In anonymous f()

巢狀類

如果不需要內部類物件與其外部類物件之間有聯絡,那麼可以將內部類宣告為 static,這通常稱為巢狀類。想要理解 static 應用於內部類時的含義,就必須記住,普通的內部類物件隱式地儲存了一個引用,指向建立它的外部類物件。然而,當內部類是 static 的時,就不是這樣了。巢狀類意味著:

1、要建立巢狀類的物件,並不需要其外部類的物件。
2、不能從巢狀類的物件中訪問非靜態的外部類物件。

巢狀類與普通的內部類還有一個區別。普通內部類的欄位與方法,只能放在類的外部層次上,所以普通的內部類不能有 static 資料和 static 欄位,也不能包含巢狀類。但是巢狀類可以包含所有這些東西:

// innerclasses/Parcel11.java
// Nested classes (static inner classes)
public class Parcel11 {
    private static class ParcelContents implements Contents {
        private int i = 11;
        @Override
        public int value() { return i; }
    }
    protected static final class ParcelDestination
            implements Destination {
        private String label;
        private ParcelDestination(String whereTo) {
            label = whereTo;
        }
        @Override
        public String readLabel() { return label; }
        // Nested classes can contain other static elements:
        public static void f() {}
        static int x = 10;
        static class AnotherLevel {
            public static void f() {}
            static int x = 10;
        }
    }
    public static Destination destination(String s) {
        return new ParcelDestination(s);
    }
    public static Contents contents() {
        return new ParcelContents();
    }
    public static void main(String[] args) {
        Contents c = contents();
        Destination d = destination("Tasmania");
    }
}

從多層巢狀類中訪問外部類的成員

一個內部類被巢狀多少層並不重要——它能透明地訪問所有它所嵌入的外部類的所有成員,如下所示:

// innerclasses/MultiNestingAccess.java
// Nested classes can access all members of all
// levels of the classes they are nested within
class MNA {
    private void f() {}
    class A {
        private void g() {}
        public class B {
            void h() {
                g();
                f();
            }
        }
    }
}
public class MultiNestingAccess {
    public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnaab = mnaa.new B();
        mnaab.h();
    }
}

可以看到在 MNA.A.B 中,呼叫方法 g() 和 f() 不需要任何條件(即使它們被定義為 private)。這個例子同時展示瞭如何從不同的類裡建立多層巢狀的內部類物件的基本語法。".new"語法能產生正確的作用域,所以不必在呼叫構造器時限定類名。

為什麼需要內部類

一般說來,內部類繼承自某個類或實現某個介面,內部類的程式碼操作建立它的外部類的物件。所以可以認為內部類提供了某種進入其外部類的視窗。

內部類必須要回答的一個問題是:如果只是需要一個對介面的引用,為什麼不通過外部類實現那個介面呢?答案是:“如果這能滿足需求,那麼就應該這樣做。”那麼內部類實現一個介面與外部類實現這個介面有什麼區別呢?答案是:後者不是總能享用到介面帶來的方便,有時需要用到介面的實現。所以,使用內部類最吸引人的原因是:

每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外部類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。

如果沒有內部類提供的、可以繼承多個具體的或抽象的類的能力,一些設計與程式設計問題就很難解決。從這個角度看,內部類使得多重繼承的解決方案變得完整。介面解決了部分問題,而內部類有效地實現了“多重繼承”。也就是說,內部類允許繼承多個非介面型別(譯註:類或抽象類)。

為了看到更多的細節,讓我們考慮這樣一種情形:即必須在一個類中以某種方式實現兩個介面。由於介面的靈活性,你有兩種選擇;使用單一類,或者使用內部類。

TODO:這章好沒意思,程式碼看著頭暈,也不知道這些設計的作用,看不下去了,以後要用的時候來補吧。