Java編程思想 -- 內部類

本博客是針對Java編程思想內部類一章所做記錄,內部類在Java隨處可見,比如集合框架中,Android源碼以及開源框架中等等。理清內部類的使用是很有必有,雖然這一章內容之前已經看過幾遍,但是還是不如梳理思路來的好。

連接到外部類

當生成一個內部類的對象時,此對象與製造它的外圍對象之間就有了一種聯繫(祕密連接到創建它的外部類),所以它能訪問其外圍對象的所有成員,而不需要任何條件

使用.this與.new

如果你需要生成對外部類對象的引用,可以使用外部類的名字後面緊跟圓點和this。

public class DotThis{
    void f(){ }
    public class Inner{
        public DotThis outer(){
            // 外部類的名字後面緊跟圓點和this
            return DotThis.this;
        }
    }
}

創建內部類對象時,必須在new表達式中提供對其他外部類對象的引用。

public class DotNew{
    public class Inner{ }

    public static void main(String[] args){
        DotNew dn = new DotNew();
        // .new、 DotNew.Inner:代表內部類
        DotNew.Inner dni = dn.new Inner();
    }
}

如果創建的是嵌套類(靜態內部類),那麼它就不需要對外部類對象的引用

內部類與向上轉型

當內部類向上轉型爲其基類或者接口時,內部類就有了用武之地,這是因爲內部類(某個接口的實現)能夠完全不可見,並且不可用,所得到的只是指向基類或者接口的引用,所以能很方便地隱藏實現細節

public interface Destination{
    String readLabel();
}

public interface Contents{
    int value();
}
class Parcel{
    private class PContents implements Contents{
        pirvate int i = 11;
        public int value() { return i; }
    }

    protected class PDestination implements Destination{
        private String label;
        private PDestination(String whereTo){
            label = whereTo;
        }
        public String readLabel() { return label; }
    }

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

    public Contents contents(){
        return new PContents();
    }
}
public class TestParcel{
    public static void main(String[] args){
        Parcel p = new Parcel();
        Contents c = p.contents();
        Destination d = p.destination("kk");

        // 失敗
        // Parcel.PContents pc = p.new PContents();
    }
}

private內部類給設計者提供一種途徑,通過這種方式可以完全阻止任何依賴於類型的編碼,並且完全隱藏了實現細節

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

也稱局部內部類

這麼做有兩個理由:
- 如前所示,你實現了某類型的接口,於是可以創建並返回對其的引用。
- 你要解決一個複雜的問題,想創建一個類來輔助你的解決方案,但又不希望這個類公共可用

匿名內部類

如果定義一個匿名內部類,並且希望它使用一個在其外部定義的對象,那麼編譯器會要求其參數引用時final的

在匿名內部類中不可能有命名構造器(因爲它根本沒有名字),但通過實例初始化(也就是{}塊),就能夠達到匿名內部類創建一個構造器的效果

abstract class Base{
    public Base(int i){
        print("Base Constructor");
    }
    public abstract void f();
}

public class AnonymousConstructor{
    public static Base getBase(int i){
        return new Base(i){
            {
                print("Inside instance initializer");
            }

            public void f(){
                print("In anonymous f()");
            }
        }
    }

    public static void main(String[] args){
        Base base = getBase(41);
        base.f();
    }
}

/*Output:
Base Constructor
Inside instance initializer
In anonymous f()
*/

匿名內部類與工廠模式

直接引用Java編程思想 – 接口 中接口與工廠模式代碼,並做修改跟對比

//具體編程技能模塊
interface ICode {
    void coding();
}

//創建技能工廠模塊
interface IFactory {
    ICode getCodingSkill();
}

class CodeImplAndroid implements ICode {
    @Override
    public void coding() {
        System.out.println("Coding Android!");
    }

    public static IFactory getFactory(){

        return new IFractory(){
            @Override
            public ICode getCodingSkill() {
                return new CodeImplAndroid();
            }
        }
    }
}

class CodeImplPHP implements ICode {
    @Override
    public void coding() {
        System.out.println("Coding PHP!");
    }

    public static IFactory getFactory(){

        return new IFractory(){
            @Override
            public ICode getCodingSkill() {
                return new CodeImplPHP();
            }
        }
    }
}

public class Main {
    public static void coding(IFactory ifactory){
        ICode code = factory.getCodingSkill();
        code.coding();
    }
}

嵌套類

普通內部類對象隱式地保存一個引用,指向創建它的外圍類對象

而嵌套類(內部類是static時),意味着:

  • 要創建嵌套類的對象,並不需要其外圍類的對象
  • 不能從嵌套類的對象中訪問非靜態的外圍類對象

嵌套類與普通的內部類還有一個區別,普通內部類的字段與方法,只能放在類的外部層次上,所以普通的內部類不能有static的數據和static字段,也不能包含嵌套類。但是,嵌套類可以包含這些東西

接口內部的類

正常情況下,不能在接口內部放置任何代碼,但嵌套類可以作爲接口的一部分
放到接口中的任何類都自動地是public和static的。甚至可以在內部類中實現其外圍接口

public interface ClassInInterface{
    void howdy();

    class Test implements ClassInInterface{
        public void howdy(){
            System.out.println("Howdy");
        }

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

/* Output:
Howdy
*/

如果你想要創建某些公共代碼,使得它們可以被某個接口的所有不同實現所共用,那麼使用接口內部的嵌套類會顯得很方便

從多層嵌套類中訪問外部類的成員

一個內部類不管被嵌套多少層,都能訪問它所嵌入的外圍類的所有成員:

class MNA{
    private void f() { } 
    class A{
        private void g() { }

        public class B{
            void h(){
                g();
                f();
            }
        }
    }
}

public class Test{
    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();
    }
}

爲什麼需要內部類

內部類必須要回答的一個問題是:如果只是需要一個對接口的引用,爲什麼不通過外圍類實現那個接口呢??

答案是:如果這能滿足需求,那麼就應該這麼做。

那麼內部類實現一個接口與外圍類實現這個接口有什麼區別呢??

答案是:後者不是總能享用到接口帶來的方便,有時需要用到接口的實現。所以使用內部類最吸引人的原因是:每個內部類都能獨立地繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。

看例子:

比如現在必須在一個類中以某種方式實現兩個接口。有兩種選擇,使用單一類,或者內部類:

interface A { }
interface B { }

class X implements A, B { }
ckass Y implements A{
    // 使用內部類
    B makeB(){
        return new B() { }
    }
}

public class MultiInterfaces {
    static void takesA(A a) { }
    static void takesB(B b) { }

    public static void main(String[] args){
        X x = new X();
        Y y = new Y();
        takesA(x);
        takesA(y);
        takesB(x);
        takesB(y.makeB());
    }
}

上面例子沒有什麼區別,都能正常運行。
但是如果擁有的是抽象的類或者具體的類,而不是接口,那就只能使用內部類才能實現多重繼承。

class D { }
abstract class E { }

class Z extends D{
    E makeE(){ return new E() { }; }
}

public class MultiImplementation{
    static void takesD(D d) { }
    static void takesE(E e) { }

    public static void main(String[] args){
        Z z = new Z();
        takesD(z);
        takesE(z.makeE());
    }
}

閉包與回調

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

內部類提供閉包功能實現回調:

Interface Incrementable{
    void increment();
}

// 
class Callee1 implements Incrementable {
    private int i = 0;
    public void increment(){
        i++;
        print(i);
    }
}

class MyIncrement{
    public void increment() { print("Other operation"); }
    static void f(MyIncrement mi) { mi.increment(); }
}

// MyIncrement類與Incrementable接口的increment方法衝突
class Callee2 extends MyIncrement{
    private int i = 0;

    public void increment(){
        super.increment();
        i++;
        print(i);
    }

    // 
    private class Closure implements Incrementable{
        public void increment(){
            // 調用外圍類的increment()方法  
            Callee2.this.increment();
        }
    }

    Incrementable getCallbackReference(){
        return new Closure();
    }
}

class Caller{
    private Incrementable callbackReference;
    Caller(Incrementable cbh) { callbackReference = cbh; }
    void go() { callbackReference.increment(); }    
}

public class Callbacks{
    public static void main(String[] args){
        Callee1 c1 = new Callee1();
        Callee2 c2 = new Callee2();

        MyIncrement.f(c2);

        Caller caller1 = new Caller(c1);
        Caller caller2 = new Caller(c2.getCallbackReference());

        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
    }
}

這個例子進一步展示了外圍類實現一個接口與內部類實現此接口之間的區別。

就代碼而言,Callee1是簡單的解決方式

Callee2繼承自MyIncrement,後者已經有了一個不同的increment()方法,並且與Incrementable接口期望的increment()方法完全不相關。所以如果Callee2繼承了MyIncrementable就不能爲了Incrementable的用途而覆蓋increment()方法,於是只能使用內部類獨立地實現Incrementable。還要注意,當創建了一個內部類時,並沒有在外圍類的接口中添加東西,也沒有修改外圍類的接口。

內部類的繼承

因爲內部類的構造器必須鏈接到指向外圍類對象的引用,所以在繼承內部類的時候,事情會變得有點複雜。

問題在於,那個指向外圍類對象的“祕密的”引用必須被初始化,而在導出類中不再存在可連接的默認對象

class WithInner{
    class Inner{ }
}

public class InheritInner extends WithInner.Inner{
    // InheritInner() { } // 不能編譯
    InheritInner(WithInner wi){
        wi.super();
    }

    public static void main(String[] args){
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

內部類可以被覆蓋嗎

如果創建一個內部類,然後繼承其外圍類並重新定義此內部類,會發生什麼??也就是說,內部類可以被覆蓋嗎??

class Egg{
    private Yolk y;
    protected class Yolk{
        public Yolk() { print("Egg.Yolk()"); }
    }

    public Egg(){
        print("New Egg");
        y = new Yolk();
    }
}

public calss BigEgg extends Egg{
    publci class Yolk{
        public Yolk() { print("BigEgg.Yolk()"); }
    }

    public staic void main(String[] args){
        new BigEgg();
    }
}

/*Output:
New Egg()
Egg.Yolk()
*/

這個例子說明,當繼承了某個外圍類的時候,內部類並沒有發生什麼變化。這兩個內部類是兩個完全獨立的兩個實體,各自在自己的命名空間內。

局部內部類

前面提到過,可以在代碼塊中創建內部類,典型的方式是在一個方法體裏創建。局部內部類不能有訪問說明符,因爲它不是外圍類的一部分;但是它可以訪問當前代碼塊的常量,以及此外圍類的所有成員。

局部內部類跟匿名內部類的區別:
局部內部類可以有構造器,而匿名內部類只能用於實例初始化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章