一篇文章讓你完全瞭解Java內部類

原文地址

什麼是內部類?

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


爲何須要內部類?

  通常來講,內部類繼承自某個類或實現某個接口,內部類的代碼操做建立其的外圍類的對象。因此你能夠認爲內部類提供了某種進入其外圍類的窗口。git


內部類的優雅之處:

每一個內部類都能獨立的繼承一個(接口的)實現,不管外部類是否已經繼承了一個(接口的)實現,對內部類都沒有影響。bash

內部類主要有如下幾類:

注意:app

  1. 定義了成員內部類後,必須使用外部類對象來建立內部類對象,而不能直接去 new 一個內部類對象,
    即:內部類 對象名 = 外部類對象.new 內部類( );
  2. 外部類是不能直接使用內部類的成員和方法滴,可先建立內部類的對象,而後經過內部類的對象來訪問其成員變量和方法。
  3. 可先建立內部類的對象,而後經過內部類的對象來訪問其成員變量和方法。HelloWorld.this.name

A:成員內部類

返回分類ide

做爲外部類的一個成員存在,與外部類的屬性、方法並列。函數

public class Outer {
    
    private static int i = 1;
    private int j = 10;
    private int k = 20;


    public static void outerF1() {
    }

    /** * 外部類的靜態方法訪問成員內部類,與在外部類外部訪問成員內部類同樣 */
    public static void outerF4() {
        //step1 創建外部類對象
        Outer out = new Outer();
        //step2 根據外部類對象創建內部類對象
        Inner inner = out.new Inner();
        //step3 訪問內部類的方法
        inner.innerF1();
    }

    public static void main(String[] args) {

        /* * outerF4();該語句的輸出結果和下面三條語句的輸出結果同樣 *若是要直接建立內部類的對象,不能想固然地認爲只需加上外圍類Outer的名字, *就能夠按照一般的樣子生成內部類的對象,而是必須使用此外圍類的一個對象來 *建立其內部類的一個對象: *Outer.Inner outin = out.new Inner() *所以,除非你已經有了外圍類的一個對象,不然不可能生成內部類的對象。由於此 *內部類的對象會悄悄地連接到建立它的外圍類的對象。若是你用的是靜態的內部類, *那就不須要對其外圍類對象的引用。 */
        Outer out = new Outer();
        Outer.Inner outin = out.new Inner();
        outin.innerF1();
    }

    public void outerF2() {
    }

    /** * 外部類的非靜態方法訪問成員內部類 */
    public void outerF3() {
        Inner inner = new Inner();
        inner.innerF1();
    }

    /** * 成員內部類中,不能定義靜態成員 * 成員內部類中,能夠訪問外部類的全部成員 */
    class Inner {
        // static int innerI = 100;內部類中不容許定義靜態變量
        // 內部類和外部類的實例變量能夠共存
        int j = 100;
        int innerI = 1;


        void innerF1() {
            System.out.println(i);
            //在內部類中訪問內部類本身的變量直接用變量名
            System.out.println(j);
            //在內部類中訪問內部類本身的變量也能夠用this.變量名
            System.out.println(this.j);
            //在內部類中訪問外部類中與內部類同名的實例變量用外部類名.this.變量名
            System.out.println(Outer.this.j);
            //若是內部類中沒有與外部類同名的變量,則能夠直接用變量名訪問外部類變量
            System.out.println(k);
            outerF1();
            outerF2();
        }
    }
}
複製代碼

注意:內部類是一個編譯時的概念,一旦編譯成功,就會成爲徹底不一樣的兩類。

對於一個名爲outer的外部類和其內部定義的名爲inner的內部類。編譯完成後出現outer.class和outer$inner.class兩類。性能


##ui

B:局部內部類

返回分類this

在方法中定義的內部類稱爲局部內部類。與局部變量相似,局部內部類不能有訪問說明符,由於它不是外圍類的一部分,可是它能夠訪問當前代碼塊內的常量,和此外圍類全部的成員。編碼

public class Outer {

    private int s = 100;
    private int outI = 1;

    public static void main(String[] args) {
        // 訪問局部內部類必須先有外部類對象
        Outer out = new Outer();
        out.f(3);
    }

    public void f(final int k) {
        final int s = 200;
        int i = 1;
        final int j = 10;


        /** * 定義在方法內部 */
        class Inner {
            // 能夠定義與外部類同名的變量
            int s = 300;
            int innerI = 100;

            // static int m = 20; 不能夠定義靜態變量
            Inner(int k) {
                innerF(k);
            }
            void innerF(int k) {
                // java若是內部類沒有與外部類同名的變量,在內部類中能夠直接訪問外部類的實例變量
                System.out.println(outI);
                // 能夠訪問外部類的局部變量(即方法內的變量),可是變量必須是final的
                System.out.println(j);
                //System.out.println(i);
                // 若是內部類中有與外部類同名的變量,直接用變量名訪問的是內部類的變量
                System.out.println(s);
                // 用this.變量名訪問的也是內部類變量
                System.out.println(this.s);
                // 用外部類名.this.內部類變量名訪問的是外部類變量
                System.out.println(Outer.this.s);
            }
        }
        new Inner(k);
    }
}
複製代碼

C:靜態內部類(嵌套類):

返回分類

注意:前兩種內部類與變量相似,因此能夠對照參考變量

若是你不須要內部類對象與其外圍類對象之間有聯繫,那你能夠將內部類聲明爲static。這一般稱爲嵌套類(nested class)。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類對象隱含地保存了一個引用,指向建立它的外圍類對象。然而,當內部類是static的時,就不是這樣了。嵌套類意味着:

  1. 要建立嵌套類的對象,並不須要其外圍類的對象。
  2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。

單例模式:因爲靜態內部類的加載機制,決定了他可使用來處理單例模式,並且性能客觀單例模式相關內容>>點我

public class Outer {
    private static int i = 1;
    private int j = 10;

    public static void outerF1() {
    }

    public static void main(String[] args) {
        new Outer().outerF3();
    }

    public void outerF2() {
    }

    public void outerF3() {
        // 外部類訪問內部類的靜態成員:內部類.靜態成員
        System.out.println(Inner.inner_i);
        Inner.innerF1();
        // 外部類訪問內部類的非靜態成員:實例化內部類便可
        Inner inner = new Inner();
        inner.innerF2();
    }

    /** * 靜態內部類能夠用public,protected,private修飾 * 靜態內部類中能夠定義靜態或者非靜態的成員 */
    static class Inner {
        static int inner_i = 100;
        int innerJ = 200;

        static void innerF1() {
            // 靜態內部類只能訪問外部類的靜態成員(包括靜態變量和靜態方法)
            System.out.println("Outer.i" + i);
            outerF1();
        }


        void innerF2() {
            // 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變量和非靜態方法)
            // System.out.println("Outer.i"+j);
            // outerF2();
        }
    }
}
複製代碼

靜態內部類和成員內部類的區別

生成一個靜態內部類不須要外部類成員 靜態內部類的對象能夠直接生成: Outer.Inner in = new Outer.Inner(); 而不須要經過生成外部類對象來生成。這樣實際上使靜態內部類成爲了一個頂級類 (正常狀況下,你不能在接口內部放置任何代碼,但嵌套類能夠做爲接口的一部分,由於它是static 的。只是將嵌套類置於接口的命名空間內,這並不違反接口的規則)


D:匿名內部類(from thinking in java 3th)

返回分類

匿名內部類就是沒有名字的內部類。

什麼狀況下須要使用匿名內部類?

若是知足下面的一些條件,使用匿名內部類是比較合適的:

  • 只用到類的一個實例。
  • 類在定義後立刻用到。
  • 類很是小(SUN推薦是在4行代碼如下)
  • 給類命名並不會致使你的代碼更容易被理解。

在使用匿名內部類時,要記住如下幾個原則:

  1. 匿名內部類通常不能有構造方法。
  2. 匿名內部類不能定義任何靜態成員、方法和類。
  3. 匿名內部類不能是public,protected,private,static。
  4. 只能建立匿名內部類的一個實例。
  5. 一個匿名內部類必定是在new的後面,用其隱含實現一個接口或實現一個類。
  6. 因匿名內部類爲局部內部類,因此局部內部類的全部限制都對其生效。

下面的例子看起來有點奇怪:

// 在方法中返回一個匿名內部類
public class Parcel6 {
    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        Contents c = p.cont();
    }

    public Contents cont() {
        return new Contents() {
            private int i = 11;


            public int value() {
                return i;
            }
        }; // 在這裏須要一個分號
    }
}
複製代碼

  cont()方法將下面兩個動做合併在一塊兒:返回值的生成,與表示這個返回值的類的定義!   進一步說,這個類是匿名的,它沒有名字。更糟的是,看起來是你正要建立一個Contents對象:

return new Contents()
複製代碼

  可是,在到達語句結束的分號以前,你卻說:「等一等,我想在這裏插入一個類的定義」:

return new Contents() {
    private int i = 11;

    public int value() {
        return i;
    }
};
複製代碼

  這種奇怪的語法指的是:「建立一個繼承自Contents的匿名類的對象。」經過new 表達式返回的引用被自動向上轉型爲對Contents的引用。匿名內部類的語法是下面例子的簡略形式:

class MyContents implements Contents {
    private int i = 11;

    public int value() {
        return i;
    }
}
return new MyContents();
複製代碼

  在這個匿名內部類中,使用了缺省的構造器來生成Contents。下面的代碼展現的是,若是你的基類須要一個有參數的構造器,應該怎麼辦:

public class Parcel7 {
    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Wrapping w = p.wrap(10);
    }

    public Wrapping wrap(int x) {
        // Base constructor call:
        // Pass constructor argument.
        return new Wrapping(x) { 
            public int value() {
                return super.value() * 47;
            }
        }; // Semicolon required
    }
}
複製代碼

  只需簡單地傳遞合適的參數給基類的構造器便可,這裏是將x 傳進new Wrapping(x)。在匿名內部類末尾的分號,並非用來標記此內部類結束(C++中是那樣)。實際上,它標記的是表達式的結束,只不過這個表達式正巧包含了內部類罷了。所以,這與別的地方使用的分號是一致的。

  若是在匿名類中定義成員變量,你一樣可以對其執行初始化操做:

public class Parcel8 {
    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Destination d = p.dest("Tanzania");
    }

    // Argument must be final to use inside
    // anonymous inner class:
    public Destination dest(final String dest) {
        return new Destination() {
            private String label = dest;

            public String readLabel() {
                return label;
            }
        };
    }
}
複製代碼

  若是你有一個匿名內部類,它要使用一個在它的外部定義的對象,編譯器會要求其參數引用是final 型的,就像dest()中的參數。若是你忘記了,會獲得一個編譯期錯誤信息。若是隻是簡單地給一個成員變量賦值,那麼此例中的方法就能夠了。可是,若是你想作一些相似構造器的行爲,該怎麼辦呢?在匿名類中不可能有已命名的構造器(由於它根本沒名字!),但經過實例初始化,你就可以達到爲匿名內部類「製做」一個構造器的效果。像這樣作:

abstract class Base {
    public 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");
            }

            public void f() {
                System.out.println("In anonymous f()");
            }
        };
    }

    public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
    }
}
複製代碼

  在此例中,不要求變量i 必定是final 的。由於i 被傳遞給匿名類的基類的構造器,它並不會在匿名類內部被直接使用。下例是帶實例初始化的「parcel」形式。注意dest()的參數必須是final,由於它們是在匿名類內被使用的。

public class Parcel9 {
    public Destinationdest(final String dest, final float price) {
        return new Destination() {
            private int cost;
            private String label = dest;

            // Instance initialization for each object:
            {
                cost = Math.round(price);
                if (cost > 100)
                    System.out.println("Over budget!");
            }

            public String readLabel() {
                return label;
            }
        };
    }

    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.dest("Tanzania", 101.395F);
    }
}
複製代碼

  在實例初始化的部分,你能夠看到有一段代碼,那本來是不能做爲成員變量初始化的一部分而執行的(就是if 語句)。因此對於匿名類而言,實例初始化的實際效果就是構造器。固然它受到了限制:你不能重載實例初始化,因此你只能有一個構造器。


從多層嵌套類中訪問外部

  一個內部類被嵌套多少層並不重要,它能透明地訪問全部它所嵌入的外圍類的全部成員,以下所示:

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」語法能產生正確的做用域,因此你沒必要在調用構造器時限定類名。


###內部類的重載問題

若是你建立了一個內部類,而後繼承其外圍類並從新定義此內部類時,會發生什麼呢?也就是說,內部類能夠被重載嗎?這看起來彷佛是個頗有用的點子,可是「重載」內部類就好像它是外圍類的一個方法,其實並不起什麼做用:

class Egg {
    private Yolk y;


    public Egg() {
        System.out.println("New Egg()");
        y = new Yolk();
    }

    protected class Yolk {
        public Yolk() {
            System.out.println("Egg.Yolk()");
        }
    }
}


public class BigEgg extends Egg {
    public static void main(String[] args) {
        new BigEgg();
    }

    public class Yolk {
        public Yolk() {
            System.out.println("BigEgg.Yolk()");
        }
    }
}
複製代碼

輸出結果爲:

New Egg() Egg.Yolk() 複製代碼

  缺省的構造器是編譯器自動生成的,這裏是調用基類的缺省構造器。你可能認爲既然建立了BigEgg 的對象,那麼所使用的應該是被「重載」過的Yolk,但你能夠從輸出中看到實際狀況並非這樣的。

  這個例子說明,當你繼承了某個外圍類的時候,內部類並無發生什麼特別神奇的變化。這兩個內部類是徹底獨立的兩個實體,各自在本身的命名空間內。固然,明確地繼承某個內部類也是能夠的:

class Egg2 {
    private Yolk y = new Yolk();


    public Egg2() {
        System.out.println("New Egg2()");
    }

    public void insertYolk(Yolk yy) {
        y = yy;
    }

    public void g() {
        y.f();
    }

    protected class Yolk {
        public Yolk() {
            System.out.println("Egg2.Yolk()");
        }


        public void f() {
            System.out.println("Egg2.Yolk.f()");
        }
    }
}


public class BigEgg2 extends Egg2 {
    public BigEgg2() {
        insertYolk(new Yolk());
    }

    public static void main(String[] args) {
        Egg2 e2 = new BigEgg2();
        e2.g();
    }

    public class Yolk extends Egg2.Yolk {
        public Yolk() {
            System.out.println("BigEgg2.Yolk()");
        }


        public void f() {
            System.out.println("BigEgg2.Yolk.f()");
        }
    }
}

複製代碼

輸出結果爲:

Egg2.Yolk()
New Egg2() Egg2.Yolk() BigEgg2.Yolk() BigEgg2.Yolk.f() 複製代碼

  如今BigEgg2.Yolk 經過extends Egg2.Yolk 明確地繼承了此內部類,而且重載了其中的方法。Egg2insertYolk()方法使得BigEgg2 將它本身的Yolk 對象向上轉型,而後傳遞給引用y。因此當g()調用y.f()時,重載後的新版的f()被執行。第二次調用Egg2.Yolk()BigEgg2.Yolk 的構造器調用了其基類的構造器。能夠看到在調用g()的時候,新版的f()被調用了。


內部類的繼承問題(thinking in java 3th p294)

由於內部類的構造器要用到其外圍類對象的引用,因此在你繼承一個內部類的時候,事情變得有點複雜。問題在於,那個「祕密的」外圍類對象的引用必須被初始化,而在被繼承的類中並不存在要聯接的缺省對象。要解決這個問題,需使用專門的語法來明確說清它們之間的關聯:

class WithInner {
    class Inner {
        Inner() {
            System.out.println("this is a constructor in WithInner.Inner");
        }

        ;
    }
}


public class InheritInner extends WithInner.Inner {
    // ! InheritInner() {} // Won't compile
    InheritInner(WithInner wi) {
        wi.super();
        System.out.println("this is a constructor in InheritInner");
    }


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


複製代碼

 輸出結果爲:

this is a constructor in WithInner.Inner
this is a constructor in InheritInner
複製代碼

  能夠看到,InheritInner 只繼承自內部類,而不是外圍類。可是當要生成一個構造器時,缺省的構造器並不算好,並且你不能只是傳遞一個指向外圍類對象的引用。此外,你必須在構造器內使用以下語法:

enclosingClassReference.super();
複製代碼

關於Java回調函數

在Java中,一般就是編寫另一個類或類庫的人規定一個接口,而後你來實現這個接口,而後把這個接口的一個對象做爲參數傳給別人的程序,別人的程序必要時就會經過那個接口來調用你編寫的函數,執行後續的一些方法,。

public class CallBack {

    public static void main(String[] args) {
        CallBack callBack = new CallBack();
        callBack.toDoSomethings(100, new CallBackInterface() {
            public void execute() {
                System.out.println("個人請求處理成功了");
            }
        });

    }

    public void toDoSomethings(int a, CallBackInterface callBackInterface) {
        long start = System.currentTimeMillis();
        if (a > 100) {
            callBackInterface.execute();
        } else {
            System.out.println("a < 100 不須要執行回調方法");
        }
        long end = System.currentTimeMillis();
        System.out.println("該接口回調時間 : " + (end - start));
    }
}
public interface CallBackInterface {

    void execute();
}
複製代碼

Java回調的實現,是否是就是基於匿名內部類實現的呢?答案是確定的.

Java裏的回調,能夠說是匿名內部類精彩表演,優美的編碼風格,真是讓人陶醉~

本文部分源代碼

相關文章
相關標籤/搜索