JAVA內部類詳解

最近在看hystrix源碼的時候,發現Netflix的牛人們,使用了大量的匿名內部類。以便更好的能夠閱讀,於是又回頭翻了翻《java編程思想》,書中所講、自己理解,記錄如下。

內部類的定義:

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

爲什麼需要內部類

    如果只是將一個類定義在另一個類的內部,那不就是相當於把兩個類簡單的組合在一塊。其實內部類實現的功能遠不止如此。它可以有以下優點:

  1. 內部類可以與外部類通信、當生成一個內部類對象的時候,此對象與製造它的外圍對象之間就有了一種聯繫,也就是內部類中隱氏的包含了外圍對象的this引用。所以它可以無條件的訪問外圍對象的所有成員

  2. 衆所周知,JAVA是單繼承,而內部類的出現、恰好從另一個思路巧妙的解決了這個問題、因爲內部類其實是一個獨立的實體,所以外圍類是否已經繼承了某個類,對於內部類是沒有影響的。內部類可以獨立的繼承任何類。又因爲其特性1,可以說,內部類是多重繼承的變向實現。

  3. 內部類具有足夠的靈活性、而且它可以使你的代碼表現的更加優雅。

內部類的分類:

  • 成員內部類
  • 局部內部類
  • 匿名內部類
  • 嵌套內部類(靜態內部類)

內部類詳解


(一)成員內部類

public class Out {
    private int outValue;
    public Out(int outValue){
        this.outValue = outValue;
    }

    class Inner{
        private int innerValue;
        public Inner(int innerValue){
            this.innerValue=innerValue;
        }
        public void printOutValue(){
            System.out.println(outValue);
        }
        public void printInnerValue(){
            System.out.println(innerValue);
        }
    }

    public static void main(String[] args){
        Out out = new Out(1);
        Out.Inner inner = out.new Inner(2);
        inner.printOutValue();
        inner.printInnerValue();
    }
}

    如果想創建內部類,你必須指明這個對象的類型:Out.Inner,在new表達式中提供對外部類對象的引用,這是需要.new語法。如示例中那樣創建。當然你也可以如此創建:Out.Inner inner = new Out(1).new Inner(2);
    當你生成了內部類的對象時,此對象與製造它的外圍對象之間就有了一種聯繫,也就是此對象隱式的包含了外圍對象的引用,你也可以在內部類中,用外部類的名稱.this【Out.this】這樣主動的獲取此引用,而且此引用在編譯器便被知曉並受到檢查,因此沒有任何運行時的成本。示例中雖然Out對象的outValue屬性是私有的,但是內部類也可以無條件訪問。這都歸功於指向外圍類的引用。正因爲它的存在,內部類自動擁有其對外圍類所有成員的訪問權。對於內部類、有一點我們應該知道,內部類被編譯後,會生成一個單獨的類。對應示例中外圍類、內部類分別被編譯爲:Out.class、Out$Inner.class
    在《java編程思想》一書以及開源的代碼中(比如hystrix),大家更傾向於把內部類定義爲私有的、對外暴漏的是內部類向上轉型的接口(向上轉型爲其基類,尤其轉型爲一個接口的時候,內部類就有了用武之地),或者只是把內部類的構造方法聲明爲私有的。但無一例外的是內部類的創建都封裝在了外部類當中。通過這些代碼,能夠開拓你以後在類設計時的思維方式。以下代碼爲迭代器設計模式的一個例子,從中可見良好的設計模式以及內部類的合理運用可以使你的代碼變得如此優雅,猶如一件精美的藝術品,使人驚訝編程原來也可以如此美。
        示例2

public 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;
        private SequenceSelector(){//禁止主動創建

        }
        @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(i);
        }
        Selector selector = sequence.selector();
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }

    }
}

(二)局部內部類

在方法和作用域內的類被稱爲局部內部類。
內部類的使用理由如下:
1)你實現了某類型的接口,於是可以創建並返回對其的引用
2)你要解決一個複雜的問題,想創建一個類來輔助你的解決方案,但是又不希望這個類是公共可用的。

  • 在方法內定義:

        示例3

public class Parcel5 {
    public Destionation destionation(String str){
        class PDestionation implements Destionation{
            private String label;
            private PDestionation(String whereTo){
                label = whereTo;
            }
            public String readLabel(){
                return label;
            }
        }
        return new PDestionation(str);
    }

    public static void main(String[] args) {
        Parcel5 parcel5 = new Parcel5();
        Destionation d = parcel5.destionation("chenssy");
    }
}

需要注意的一點:在destionation()方法內定義了PDestionation,
但並不意味着一旦destionation()方法執行完成,PDestionation就不可用了。

  • 在作用域內定義

            示例4

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("qcx");
            String string = ts.getSlip();
        }
    }

    public void track(){
        internalTracking(true);
    }

    public static void main(String[] args) {
        Parcel6 parcel6 = new Parcel6();
        parcel6.track();
    }
}

需要注意的一點:TrackingSlip類被嵌入到if語句的作用域內,這並不是說該類的創建是有條件的,它其實與別的類一起編譯過了。只是,在作用域之外,它是不可用的,除此之外,它與普通的類一樣。

(三)匿名內部類

        示例5

public 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;
    }

    public Selector selector(){
        return new 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 static void main(String[] args){
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++){
            sequence.add(i);
        }
        Selector selector = sequence.selector();
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

selector()方法將返回值的生成與表示這個返回值的類的定義結合在一起!另外這個類是匿名的,它沒有名字。你也可以這樣理解:創建一個實現Selector接口的匿名類對象。(當然,要基於Selector接口或類存在的前提)通過new表達式返回的引用自動向上轉型爲對Selector的引用。其實他就是示例2代碼的一種簡化形式。不過有一點是需要引起大家注意的匿名類缺少了訪問修飾符
在這個匿名內部類中,使用了默認的構造器來生成了Selector對象,如果你的基類需要一個有參構造參數,應該怎麼辦。示例6:構造器將要求傳遞一個參數。

        示例6

public abstract class Selector {
    protected int current;
    public Selector(int current){
        this.current=current;
    }
    abstract boolean end();
    abstract Object current();
    abstract 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;
    }

    public Selector selector(int current){
        return new Selector(current){
            @Override
            public boolean end() {
                return current == items.length;
            }
            @Override
            public Object current() {
                return items[current];
            }
            @Override
            public void next() {
                if(current<items.length)
                    current++;
            }
        };
    }

    public static void main(String[] args){
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++){
            sequence.add(i);
        }
        Selector selector = sequence.selector(0);
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

如果定義一個匿名內部類,並且希望它使用一個在其外部定義的對象,那麼編譯器將會要求其參數引用是final的。就像示例7中selector(final int i)的參數一樣。如果你忘記了,將會得到一個編譯時的錯誤提示。
        示例7

public 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;
    }

    public Selector selector(final int i){
        return new Selector(){
            private int current=i;
            @Override
            public boolean end() {
                return current == items.length;
            }
            @Override
            public Object current() {
                return items[current];
            }
            @Override
            public void next() {
                if(current<items.length)
                    current++;
            }
        };
    }

    public static void main(String[] args){
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++){
            sequence.add(i);
        }
        Selector selector = sequence.selector(1);
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

如果只是簡單地給一個字段賦值,那麼此例中的方法是很好的,但是想做一些構造器的行爲呢?在匿名內部類中不可能有命名構造器(因爲它根本沒有名字),但通過示例初始化,就能夠達到爲匿名內部類構建一個構造器的效果,就像下面 示例中一樣。
        示例8

public abstract class Selector {
    protected int current;
    public Selector(int current){
        this.current=current;
    }
    abstract boolean end();
    abstract Object current();
    abstract 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;
    }

    public Selector selector(int current){
        return new Selector(current){
            {//實例初始化
                this.current=current*2;
            }
            @Override
            public boolean end() {
                return this.current == items.length;
            }
            @Override
            public Object current() {
                return items[this.current];
            }
            @Override
            public void next() {
                if(this.current<items.length)
                    this.current++;
            }
        };
    }

    public static void main(String[] args){
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++){
            sequence.add(i);
        }
        Selector selector = sequence.selector(2);
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

在此示例中,selector(int current)方法中的參數,不被要求爲final的。因爲current被傳遞給匿名類的基類構造器,它並不會在匿名類內部被直接使用。

(四)嵌套內部類

如果不需要內部類對象與外圍類對象之間有聯繫,那麼可以將內部類聲明爲static。這通常稱爲嵌套類。普通的內部類對象隱式地保存了一個引用,指向創建它的外圍類對象。而內部類卻沒有此引用。這意味着:

  • 要創建嵌套類對象,並不需要外圍類對象。
  • 不能從嵌套類的對象中訪問非靜態的外圍類對象
  • 普通的內部類不能有static數據與static字段,也不能包含嵌套類。但是嵌套類可以包含所有這些東西。
  • 普通類的創建方式:new 外圍類.new 內部類
    嵌套類的創建方式:new 外圍類.內部類

         示例9

public interface Selector {
    boolean end();
    Object current();
    void next();
}
public class Sequence {
    private static 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;
    }

    public static 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 static void main(String[] args){
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++){
            sequence.add(i);
        }
        SequenceSelector selector = new Sequence.SequenceSelector();        

        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

4.1接口內部類

正常情況下,不能在接口內部放置任何代碼,但嵌套類可以作爲接口的一部分。你放到接口中的任何類都自動地是public和static的。而嵌套類正式static的,這並不違反接口的規則。更酷的是,你甚至可以在嵌套類中實現這個接口。

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

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

4.2多層嵌套類

一個內部類被嵌套多少層並不重要——它能透明地訪問所有它所嵌入地外圍類地所有成員。

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

    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();
    } 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章