java編程思想筆記4(ch10:內部類)

ch10:內部類(inner class)

定義

將一個類的定義放在另一個類的定義內部,這就是內部類。

創建內部類

創建內部類的方式就如定義一樣,放置在外圍類裏面就行:

public class Parcel1 {
    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;
        }
    }
    // using inner classes looks just like
    // using any other class. withing Parcel1:
    public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLabel());
    }
    public static void main(String[] args) {
        Parcel1 p = new Parcel1();
        p.ship("Tasmania");
    }
    /**
     * output:
     * Tasmania
     */
}

在ship()方法裏面使用內部類時,與普通類沒有什麼區別。
更常用的是外圍類定義一個方法只想內部類,代碼如下:

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 class:
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("Borneo");
    }
    /**
     * output:
     * Tasmania
     */
}

如果想從外部類的非靜態方法之外的任意位置創建某個內部類對象,那麼必須指明具體的外部類名:OuterClassName.InnerClassName

鏈接到外部類

以上講的內部類還只是一種代碼隱藏和組織代碼的模式。
當然還有其他用途,當生成一個內部類對象時,此對象與製造它的外圍對象(enclosing object)之間就有了一種聯繫,所以可以隨意訪問外圍對象的所有成員,不需要任何特殊條件。此外,還擁有外圍類的所有元素的訪問權。
示例代碼如下:

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();
        }
    }
    /**
     * output:
     * 0 1 2 3 4 5 6 7 8 9
     */
}

內部類可以任意訪問外部類的元素。

使用.this與.new

如果需要生成對外部對象的引用,可以使用外部類加.this。這樣產生的引用自動地具有正確的類型,這一點在編譯器知曉識別了,因此沒有任何運行時開銷。

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"
            //如果僅用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();
    }
    /**
     * output:
     * DotThis.f()
     */
}

直接創建內部類對象需要提供外部類對象的引用,使用.new語法創建。如下:

public class DotNew {
    public class Inner {}
    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}

注意,必須在有外部類的情況下才能創建內部類對象,因爲內部類會暗暗地連接到創建它的外部類對象上,對於嵌套類(靜態內部類),就可以不需要外部類對象的引用(也就是可以直接創建內部類對象)。

在方法和作用域內的內部類

內部類的位置是靈活的,在外部類裏面,外部類的方法裏面,外部類的代碼塊中都可以,只是位置決定了該內部類的作用範圍,使用的時候需要注意。

public class Parcel5 {
    public Destination destination(String s) {
        class PDestination implements Destination {
            private String label;
            private PDestination(String whereTo) {
                label = whereTo;
            }
            @Override
            public String readLabel() {
                return label;
            }
        }
        return new PDestination(s);
    }
    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
    }
}

PDestination是destination()方法的一部分,作用點僅在方法內部。返回的是向上轉型的Destination。

public class Parcel6 {
    private void internalTracking(boolean b) {
        if (b) {
            class TrackingSlip {
                private String id;
                TrackingSlip(String s) {
                    id = s;
                }
                String getSlip() {
                    return id;
                }
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
        }
        //can't use it here! Out of scope:
        //! TrackingSlip ts = new TrackingSlip("x");
    }
    public void track() {
        internalTracking(true);
    }
    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        p.track();
    }
}

上面的代碼,TrackingSlip類被嵌入在if語句的作用域內,並不是說該類的創建是有條件的,它其實與別的類一起編譯過了。除了作用域外,與普通類一樣。

匿名內部類

創建一個繼承自接口或者類的一個匿名類的對象,通過new表達式返回的引用被自動向上轉型爲對父類(接口或者類)的引用。

class Contents {
    private int i = 11;
    public int value() {
        return i;
    }
}
public class Parcel7 {
    public Contents contents() { // insert a class definition
        return new Contents() {
            private int  i = 12;
            public int value() {
                return i;
            }
        }; // semicolon required in this case
    }
    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
}

以上展示的通過匿名內部類向上轉型父類contents,該contents是一個實體類,一般而言,我們創建的匿名內部類以接口居多,上面展示的是對於類也是可以使用該方式。
對於需要有參數構造器的,以下展示的是帶構造參數的匿名內部類。

class Wrapping {
    private int x;
    public Wrapping(int x) {
        this.x = x;
    }
    int value() {
        return x;
    };
}
public class Parcel8 {
    //構造器參數x不必是final的
    public Wrapping wrapping(int x) {
        //base constructor call:
        return new Wrapping(x) {
            @Override
            public int value() {
                return super.value() * 47;
            }
        };// semicolon required
    }
    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
    }
}

在匿名內部類末尾的分號,並不是用來標記此內部類結束的,只是該表達式的結束,只不過這個表達式正巧包含了匿名內部類罷了。
在匿名類中定義字段時,還能夠對其執行初始化操作:

public class Parcel9 {
    //匿名類對象中初始化的參數dest必須是final的
    public Destination destination(final String dest) {
        return new Destination() {
            private String label = dest;
            @Override
            public String readLabel() {
                return label;
            }
        };
    }
    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination("Tasmania");
    }
}

匿名內部類構造器方法傳入的參數因爲是初始化時就定了,所以不必要求是final的,但是對於匿名內部類初始化時的參數,必須是final的。

嵌套類

如果不需要內部類對象與外圍類對象有聯繫,可以將內部類聲明爲static。這個通常稱爲嵌套類
普通內部類對象隱式地保存了一個引用,指向創建它的外圍類。
使用嵌套類需要注意:
1. 創建嵌套類,不需要外圍對象
2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。

示例代碼如下:

public class Parcel11 {
    private String value1 = "value1";
    private static String value2 = "value2";
    public static class Inner {
        public String getValue() {
            return "value";
            //無法獲取到value1的值,因爲對於該嵌套類來說是單獨存在的,不能訪問非靜態變量
            //! return value1;
        }
        //可以獲取到靜態變量的值
        public String getValue2() {
            return value2;
        }
    }
    public static void main(String[] args) {
        //直接使用new OuterClassName.InnerClassName()創建嵌套類對象,
        // 前提是嵌套類的在這裏是可見的
        System.out.println(new Parcel11.Inner().getValue());
    }
}

接口內部類

正常情況下,不能在接口中放置任何代碼,但是嵌套類可作爲接口的一部分存在,因爲實在接口中,所以該類自動的爲public和static屬性。因爲類是static的,所以只是將嵌套類置於接口的命名空間內,並不違反接口的規則。
示例:

public interface ClassInterface {
    void howdy();
    class Test implements ClassInterface {
        @Override
        public void howdy() {
            System.out.println("howdy()");
        }
        public static void main(String[] args) {
            new Test().howdy();
        }
    }
}

多層嵌套類

一個類中可以嵌套很多層,這個沒有限制。
示例:

class MNA {
    private void f() {}
    class A {
        private void g() {}
        public class B {
            void h() {
                g();
                f();
            }
        }
    }
}
public class MutiNestingAccess {
    public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnab = mnaa.new B();
        mnab.h();
    }
}

爲什麼需要內部類

每個內部類都能獨立地繼承一個(接口)的實現,所以無論外圍類是否已經繼承了某個(接口)的實現,對於內部類是沒有影響的。
對於實現多接口,外部類一個即可以實現,內部類也同樣可以,但是對於繼承(普通類和抽象類),只有通過內部類解決,即內部類比較完美的完善了多繼承的問題,相對於c++來說(實現多接口只是一部分上解決了這個問題)。

class D {}
abstract class E {}
class Z extends D {
    E makeE() {
        return new E() {};
    }
}
public class MutiImplementation {
    static void taskD(D d) {}
    static void taskE(E e) {}
    public static void main(String[] args) {
        Z z = new Z();
        taskD(z);
        taskE(z.makeE());
    }
}

如果不需要解決“多重繼承”的問題,自然可以使用其他方式解決,可以不使用內部類,但是如果使用內部類,還可以獲得其他的一些特性:
1. 內部類可以有多個實例,每個實例都有自己的狀態信息,並且與其外圍類對象的信息相互獨立。
2. 在單個外圍類,可以讓多個內部類以不同的方式實現同一個接口,或繼承同一個類。
3. 創建內部類對象的時刻並不依賴於外圍類對象的創建。
4. 內部類並沒有令人迷惑的“is-a”關係;它就是一個獨立的實體。

  • 閉包與回調
    閉包(closure)是一個可調用的對象,它記錄了一些信息,這些信息來自於創建它的作用域。
    通過這個定義可以看出,內部類是面向對象的閉包,因爲它不僅包含外圍類對象(創建內部類的作用域)的信息,還自動擁有一個指向外圍類對象的引用,在此作用域內,內部類有權限操作所有成員,包括private成員。

內部類的繼承

內部類有點特殊,因爲其必須持有外圍類的引用才能夠進行初始化,所以以下示例展示了內部類的繼承問題:

class WithInner {
    class Inner {}
}
public class InheritInner extends WithInner.Inner {
    //不能使用默認構造器
    //InheritInner() {}  該構造器不會編譯
    InheritInner(WithInner withInner) {
        withInner.super();
    }
    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

如上示例代碼,繼承自內部類,必須在構造器方法中調用外部類中調用
enclosingClassReference.super()提供必要的引用,方法才能編譯通過。

內部類可以被覆蓋嗎

創建一個內部類,然後繼承其外圍類並重新定義此內部類時,以下爲示例代碼:

class Egg {
    private Yolk y;
    protected class Yolk {
        public Yolk() {
            System.out.println("Egg.Yolk()");
        }
    }
    public Egg() {
        System.out.println("new Egg()");
        y = new Yolk();
    }
}
public class BigEgg extends Egg {
    //此處被不能“覆蓋”基類中的內部類
    public class Yolk {
        public Yolk() {
            System.out.println("BigEgg.Yolk()");
        }
    }
    public static void main(String[] args) {
        new BigEgg();
    }
    /**
     * output:
     * new Egg()
     * Egg.Yolk()
     */
}

內部類可以明確的繼承,示例:

class Egg2 {
    protected class Yolk {
        public Yolk() {
            System.out.println("Egg2.Yolk()");
        }
        public void f() {
            System.out.println("Egg2.Yolk.f()");
        }
    }
    private Yolk y = new Yolk();
    public Egg2() {
        System.out.println("New Egg2()");
    }
    public void insertYolk(Yolk yy) {
        y = yy;
    }
    public void g() {
        y.f();
    }
}
public class BigEgg2 extends Egg2 {
    public class Yolk extends Egg2.Yolk {
        public Yolk() {
            System.out.println("BigEgg2.Yolk()");
        }
        public void f() {
            System.out.println("BigEgg2.Yolk.f()");
        }
    }
    public BigEgg2() {
        insertYolk(new Yolk());
    }
    public static void main(String[] args) {
        Egg2 e2 = new BigEgg2();
        e2.g();
    }
    /**
     * output:
     * Egg2.Yolk()
     * New Egg2()
     * Egg2.Yolk()
     * BigEgg2.Yolk()
     * BigEgg2.Yolk.f()
     */
}

局部內部類

在代碼塊裏面也可以使用類,這稱之爲局部內部類,其作用範圍是有限的。
局部內部類不能有訪問限制符,因爲不是外圍類的一部分,但是可以訪問當前代碼塊內的常量,以及此外圍類的所有成員。

內部類標識符

由於每個類會產生一個.class文件,其中包含了如何創建該類型的對象的全部信息(此信息產生一個“meta-class”,叫做Class對象),內部類也必須生成一個.class文件包含它們的Class對象信息。這些類文件的命名有嚴格的規則:外圍類的名字,加上“$”,再加上內部類的名字。
如果內部類時匿名的,編譯器會簡單的產生一個數字作爲其標識符。如果內部類時嵌套在別的內部類中,只需直接將它們的名字加載其外圍類標識符與“$”的後面。

//簡單的獲取類名的示例代碼
class Test{
    class Inner {
        public class Inner1 {}
    }
}
public class ClassNameTest {
    public static void main(String[] args) {
        Test t= new Test();
        Test.Inner inner1 = t.new Inner();
        Test.Inner.Inner1 inner11 = inner1.new Inner1();
        System.out.println(inner1.getClass().getName());
        System.out.println(inner11.getClass().getName());
    }
    /**
     * output:
     * Test$Inner
     * Test$Inner$Inner1
     */
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章