Java重新出發(fā)--Java學(xué)習(xí)筆記(八)--內(nèi)部類(lèi)相關(guān)

1.內(nèi)部類(lèi)概述

一個(gè)類(lèi)的定義放在另一個(gè)類(lèi)的內(nèi)部浴栽,這個(gè)類(lèi)就叫做內(nèi)部類(lèi)。內(nèi)部類(lèi)是一種非常有用的特性,允許把一些邏輯相關(guān)的類(lèi)組織在一起最冰。
內(nèi)部類(lèi)大體上可以分為四種:
成員內(nèi)部類(lèi),靜態(tài)內(nèi)部類(lèi)稀火,局部?jī)?nèi)部類(lèi)暖哨,匿名內(nèi)部類(lèi)
首先按照順序了解一下這四種內(nèi)部類(lèi)的特點(diǎn)。

2.成員內(nèi)部類(lèi)

成員的內(nèi)部類(lèi)是最常見(jiàn)也是最基礎(chǔ)的內(nèi)部類(lèi)憾股,沒(méi)有那些花里胡哨的修飾:

//外部類(lèi)
public class Outer {
    private String a = "a";
    public int i = 1;
    //內(nèi)部類(lèi)
    class Inner{
        private String b = "b";
        public String c = "c";

        public int getInt(){
            return i; // 內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)變量
        }

        private String getString(){
            return a + b + c; // 內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)的private變量
        }
    }

    public String getParam(){
        Inner inner = new Inner();
        inner.b = "bb"; // 外部類(lèi)可以訪問(wèn)內(nèi)部類(lèi)的private變量
        inner.c = "cc";
        return inner.getInt() + inner.getString();
    }
}
//測(cè)試類(lèi)
class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        System.out.println(outer.getParam()); // 輸出:1abbcc

        Outer.Inner oi = outer.new Inner();
        oi.c = "ccc";
        //oi.b = "bbb";  編譯失敗
        System.out.println(oi.getInt()); // 輸出:1
        //System.out.println(oi.getString()); 編譯失敗
    }
}

從以上代碼可以總結(jié)出以下普通內(nèi)部類(lèi)的特點(diǎn):

  • 內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)變量鹿蜀,包括私有變量
  • 在外部類(lèi)中使用內(nèi)部類(lèi)的方法需要new一個(gè)內(nèi)部類(lèi)的對(duì)象。
  • 在外部類(lèi)中可以訪問(wèn)到內(nèi)部類(lèi)的任何變量服球,包括私有變量茴恰。
  • 在其它類(lèi)中創(chuàng)建內(nèi)部類(lèi)對(duì)象需要使用這樣的形式:
    OuterClassName.InnerClassName oi = new OuterClassName().new InnerClassName();

在其他類(lèi)中定義的內(nèi)部類(lèi)對(duì)象不能訪問(wèn)內(nèi)部類(lèi)中的私有變量。除此之外斩熊,內(nèi)部類(lèi)中還可以通過(guò).this訪問(wèn)到外部類(lèi)的對(duì)象往枣。

public class Outer{
    private int num ;
    public Outer(){}
    
     public Outer(int num){
        this.num = num;
    }
    
    private class Inner{
        public Outer getTest2(){
            return Outer.this; // Outer.this指的是外部類(lèi)的對(duì)象
        }

        public Outer newTest2(){
            return new Outer();
        }
    }
    
     public static void main(String [] args){
        Outer test = new Outer(5);
        Outer.Inner inner = test.new Inner();
        Outer o1 = inner.getTest2();
        Outer o2 = inner.newTest2();
        System.out.println(o1.num); // 5
        System.out.println(o2.num); // 0 這個(gè)是新創(chuàng)建了一個(gè)外部類(lèi)對(duì)象
    }
}

這里需要注意的是,通過(guò).this獲得對(duì)象是不同于new出來(lái)的對(duì)象的。使用.this后分冈,得到的是創(chuàng)建該內(nèi)部類(lèi)時(shí)使用的外圍類(lèi)對(duì)象的引用圾另,new則是創(chuàng)建了一個(gè)新的引用。

內(nèi)部類(lèi)是個(gè)編譯時(shí)期的概念雕沉,一旦編譯成功它和外部類(lèi)是兩個(gè)完全不同的類(lèi)集乔。(當(dāng)然內(nèi)外部類(lèi)之間還是有聯(lián)系的)

對(duì)于一個(gè)名為OuterClass的外圍類(lèi)和一個(gè)名為InnerClass的內(nèi)部類(lèi),在編譯成功后坡椒,會(huì)出現(xiàn)這樣兩個(gè)class文件:OuterClass.classOuterClass$InnerClass.class.

3.內(nèi)部類(lèi)與向上轉(zhuǎn)型

目前看來(lái)只是給類(lèi)中隱藏了一個(gè)類(lèi)扰路,java本身就自帶這種隱藏機(jī)制,只要給某個(gè)類(lèi)包訪問(wèn)權(quán)限就好倔叼,也是用不著創(chuàng)建內(nèi)部類(lèi)的吧汗唱。但是,當(dāng)一個(gè)內(nèi)部類(lèi)向上轉(zhuǎn)型為其基類(lèi)丈攒,尤其是轉(zhuǎn)型為一個(gè)接口時(shí)哩罪,內(nèi)部類(lèi)就有了用武之地。這是因?yàn)檫@樣的內(nèi)部類(lèi)(某個(gè)接口的實(shí)現(xiàn)類(lèi))對(duì)于其他人完全不可見(jiàn)且不可用巡验,所得到的只是指向基類(lèi)或者接口的引用际插,所以能很方便的隱藏實(shí)現(xiàn)細(xì)節(jié)。
以代碼為例:

//定義兩個(gè)接口
public interface Run{
    void run();
}
public interface Eat{
    void eat();
}
//外部類(lèi)
public class Person{
    //這里是private
    private class PEat implements Eat{
        @Override
        public void eat(){
            System.out.println("eat with mouse");
        }
    }
       //這里是protected
    protected class PRun implements Run{
        @Override
        public void run() {
            System.out.println("run with leg");
        }
    }
    
    public Eat howToEat(){
        return new PEat();//向上轉(zhuǎn)型
    }
    public Run houToRun(){
        return new PRun(); //向上轉(zhuǎn)型
    }
}
class TestPerson{
    public static void main(String[] args) {
        Person p = new Person();
        Eat e = p.howToEat();
        Run r = p.houToRun();
        
        e.eat();
        r.run();
        
        Person.PRun ppr = p.new PRun();
        //Person.PEat ppe = p.new PEat(); 編譯失敗,因?yàn)镻Eat是private的
    }
}

從這段代碼可以看出显设,PEat是private腹鹉,所以除了Person(它的外部類(lèi)),沒(méi)有人能訪問(wèn)到它敷硅。
PRun是protected功咒,所以只有Person及其子類(lèi)、還有與Person同一個(gè)包中的類(lèi)能訪問(wèn)PRun绞蹦,其他類(lèi)不能訪問(wèn)力奋。
這意味著,如果客戶(hù)端想要訪問(wèn)這些成員是要受到限制的幽七,除此之外景殷,private內(nèi)部類(lèi)也不可被向下轉(zhuǎn)型,因?yàn)闊o(wú)法訪問(wèn)到它澡屡。所以private內(nèi)部類(lèi)給類(lèi)的設(shè)計(jì)者提供了一種途徑猿挚,通過(guò)這樣的方式可以完全阻止任何依賴(lài)于類(lèi)的編碼,并且隱藏了實(shí)現(xiàn)的細(xì)節(jié)驶鹉。此外绩蜻,對(duì)于客戶(hù)端程序員來(lái)說(shuō),由于不能訪問(wèn)任何新增加的室埋,原本不屬于公共接口的方法办绝,所以擴(kuò)展接口是沒(méi)有價(jià)值的伊约。這也給java編譯器提供了更高效代碼的機(jī)會(huì)。所以說(shuō)一般成員內(nèi)部類(lèi)孕蝉,都會(huì)定義成private.
普通的類(lèi)(非內(nèi)部類(lèi))屡律,不能聲明為private或protected,它們只給你被賦予public或者包訪問(wèn)權(quán)限降淮。

4.靜態(tài)內(nèi)部類(lèi)(嵌套類(lèi))

如果不需要內(nèi)部類(lèi)對(duì)象與其它外圍類(lèi)對(duì)象之間有聯(lián)系超埋,可以將內(nèi)部類(lèi)設(shè)置為static.這就是靜態(tài)內(nèi)部類(lèi),也稱(chēng)為嵌套類(lèi)佳鳖。普通的內(nèi)部類(lèi)對(duì)象隱式的保存了一個(gè)指向它外部類(lèi)引用的變量纳本,所以可以無(wú)條件的使用外部類(lèi)的變量,但是內(nèi)部類(lèi)用static修飾時(shí)腋颠,就不會(huì)有這個(gè)變量了。這也意味著:要?jiǎng)?chuàng)建嵌套類(lèi)的對(duì)象吓笙,并不需要其外圍類(lèi)的對(duì)象淑玫。
靜態(tài)內(nèi)部類(lèi)中不能訪問(wèn)非靜態(tài)的外部類(lèi)變量,但是可以訪問(wèn)外部類(lèi)的靜態(tài)變量面睛。
除此之外絮蒿,由于普通內(nèi)部類(lèi)的字段與方法,只能放在類(lèi)的外部層次上叁鉴,所以普通的內(nèi)部類(lèi)不能有static方法和static變量土涝,也不能在普通內(nèi)部類(lèi)中再包含靜態(tài)內(nèi)部類(lèi)。
但是靜態(tài)內(nèi)部類(lèi)可以包含所有這些東西:

public class Outer{
    private int i =1;
    public static String str = "str";
    
    static class StaClass implements inter{
        private String s = "s";
        static int j = 2;
        
        static int getInt(){
            //return i+j幌墓;//訪問(wèn)不到i非靜態(tài)
            return j;
        }
        private String getString(){
            return str + s;
        }
        
        @Override
        public void inter() {
            System.out.println("inter");
        }
        static class InStaClass{
            int x = 4;
            static int y = 5;
            static int getInt(){
                //return x; // x是非靜態(tài)變量 不可以在靜態(tài)方法中使用
                return y;
            }
        }
    }
    public inter getInter(){
        return new StaClass();
    }
}
class Test{
    public static void main(String[] args) {
        int a = Outer.StaClass.getInt();

        //Outer.StaClass.getString(); // getString()為非靜態(tài)方法但壮,不能這樣調(diào)用

        int b = Outer.StaClass.InStaClass.getInt();

        System.out.println(a + "----" + b); // 輸出 2----5

        //new Outer().new StaClass(); 編譯失敗 StaClass是靜態(tài)的

        new Outer().getInter().inter(); // 輸出 inter
    }
}

這里總結(jié)一下靜態(tài)內(nèi)部類(lèi)的要點(diǎn):

  • 在靜態(tài)內(nèi)部類(lèi)可以存在靜態(tài)成員。
  • 靜態(tài)內(nèi)部類(lèi)只能訪問(wèn)外圍類(lèi)的靜態(tài)成員變量和方法常侣,不能訪問(wèn)外圍類(lèi)的非靜態(tài)成員變量和方法蜡饵。
  • 靜態(tài)內(nèi)部類(lèi)中的靜態(tài)方法,可以通過(guò)外部類(lèi).內(nèi)部類(lèi).方法名直接調(diào)用
  • 靜態(tài)內(nèi)部類(lèi)在其它類(lèi)中不能被new出來(lái)胳施,new Outer().new StaClass()這樣是不行的溯祸,但是在外部類(lèi)中,可以new一個(gè)靜態(tài)內(nèi)部類(lèi)的對(duì)象舞肆。
  • 靜態(tài)內(nèi)部類(lèi)中不能使用.this,因?yàn)闆](méi)有默認(rèn)的引用

5.局部?jī)?nèi)部類(lèi)

在方法里或者任何作用域里定義的內(nèi)部類(lèi)叫做局部?jī)?nèi)部類(lèi)焦辅。如前所示,你實(shí)現(xiàn)了某類(lèi)型的接口椿胯,于是你可以創(chuàng)建并返回對(duì)其的引用筷登,你需要這樣的引用。
你要解決一個(gè)復(fù)雜的問(wèn)題哩盲,想創(chuàng)建一個(gè)類(lèi)來(lái)輔助你的解決方案仆抵,但是又不希望這個(gè)類(lèi)是公共可用的跟继。

5.1一個(gè)定義在方法中的類(lèi)

public class Person {
    public Eat howToEat(){
        // 定義在方法中的類(lèi)
        class EatWithMouth implements Eat{
            @Override
            public void eat() {
                System.out.println("eat with mouth");
            }
        }
        // 向上轉(zhuǎn)型
        return new EatWithMouth();
    }

    public static void main(String[] args) {
        Eat e = new Person().howToEat();
        e.eat(); // eat with mouth
    }
}

EatWithMouth是方法howToEat中的類(lèi)而不是Person中的類(lèi),你甚至可以在同一個(gè)子目錄下的任意一個(gè)類(lèi)中給任意一個(gè)內(nèi)部類(lèi)起EatWithMouth這個(gè)名字镣丑,而不會(huì)由命名沖突舔糖。當(dāng)然在howToEat方法外的任何地方都不能訪問(wèn)到EatWithMouth類(lèi)。但是這并不意味一旦howToEat方法執(zhí)行完畢莺匠,EatWithMouth類(lèi)就不能用了金吗。

5.2在任意作用域嵌入一個(gè)內(nèi)部類(lèi)

public class EveryBlock {
    private String test(boolean b){
        if (b){
            class A{
                private String a = "a";
                String getString(){
                    return a;
                }
            }
            A a = new A();
            String s = a.getString();
            return s;
        }
        //A a = new A();  編譯失敗 超出作用域
        return null;
    }

    public static void main(String[] args) {
        EveryBlock eb = new EveryBlock();
        System.out.println(eb.test(true)); // a
    }
}

雖然這個(gè)類(lèi)A是在條件語(yǔ)句中,但是它的創(chuàng)建是無(wú)條件的趣竣,和其它類(lèi)一樣進(jìn)行編譯摇庙。僅僅只是作用域不同而已。通過(guò)這樣的方式遥缕,就解決了上面提到的第二個(gè)問(wèn)題:不希望這個(gè)類(lèi)是公用的卫袒。

6.匿名內(nèi)部類(lèi)

匿名內(nèi)部類(lèi)使用的地方有很多,看一個(gè)例子:

public class OuterClass{
    public InnerClass getInnerClass(final int num,String str2){
        return new InnerClass(){
            int number = num + 3;
            public int getNumber(){
                return number;
            }
        };
    }
     public static void main(String[] args) {
        OuterClass out = new OuterClass();
        InnerClass inner = out.getInnerClass(2, "chengfan");
        System.out.println(inner.getNumber());
    }
}
interface InnerClass{
    int getNumber();
}

這段代碼里有一段很奇怪的東西:

        return new InnerClass(){
            int number = num + 3;
            public int getNumber(){
                return number;
            }
        };

這不是一個(gè)接口嗎?沒(méi)錯(cuò)這就是匿名內(nèi)部類(lèi)单匣。事實(shí)上夕凝,這段代碼和下面的寫(xiě)法是等價(jià)的:

public class OuterCla {
    class InnerClassImpl implements InnerClass{
        int number ;
        public InnerClassImpl(int num){
            number = num + 3;
        }
        public int getNumber(){
            return number;
        }
    }
    public InnerClass getInnerClass(final int num){
        return new InnerClassImpl(2);
    }

    public static void main(String[] args) {
        OuterCla out = new OuterCla();
        InnerClass inner = out.getInnerClass(2);
        System.out.println(inner.getNumber());
    }
}

這段代碼你應(yīng)該懂了。將兩段代碼一比較户秤,你大概也清楚了码秉,上面那樣寫(xiě),意思是創(chuàng)建了一個(gè)實(shí)現(xiàn)了InnerClass的匿名類(lèi)的對(duì)象鸡号。
匿名類(lèi)可以創(chuàng)建接口转砖、抽象類(lèi)、與普通類(lèi)的對(duì)象鲸伴。創(chuàng)建接口和抽象類(lèi)時(shí)府蔗,必須實(shí)現(xiàn)接口中所有方法。 創(chuàng)建匿名類(lèi)時(shí)汞窗,可以是無(wú)參的礁竞,也可以有參數(shù)的,但是如果這個(gè)參數(shù)要在匿名類(lèi)中使用杉辙,參數(shù)必須是final的模捂,如果不使用,可以不被final修飾(代碼中有體現(xiàn))蜘矢。

6.1為什么必須是final呢狂男?

首先我們知道在內(nèi)部類(lèi)編譯成功后,它會(huì)產(chǎn)生一個(gè)class文件品腹,該class文件與外部類(lèi)并不是同一class文件岖食,僅僅只保留對(duì)外部類(lèi)的引用。當(dāng)外部類(lèi)傳入的參數(shù)需要被內(nèi)部類(lèi)調(diào)用時(shí)舞吭,從java程序的角度來(lái)看是直接被調(diào)用:

public class OuterClass {
    public void display(final String name,String age){
        class InnerClass{
            void display(){
                System.out.println(name);
            }
        }
    }
}

從上面代碼中看好像name參數(shù)應(yīng)該是被內(nèi)部類(lèi)直接調(diào)用泡垃?其實(shí)不然析珊,在java編譯之后實(shí)際的操作如下:

public class OuterClass$InnerClass {
    public InnerClass(String name,String age){
        this.InnerClass$name = name;
        this.InnerClass$age = age;
    } 
    public void display(){
        System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
    }
}

所以從上面代碼來(lái)看,內(nèi)部類(lèi)并不是直接調(diào)用方法傳遞的參數(shù)蔑穴,而是利用自身的構(gòu)造器對(duì)傳入的參數(shù)進(jìn)行備份忠寻,自己內(nèi)部方法調(diào)用的實(shí)際上時(shí)自己的屬性而不是外部方法傳遞進(jìn)來(lái)的參數(shù)。

直到這里還沒(méi)有解釋為什么是final存和?
在內(nèi)部類(lèi)中的屬性和外部方法的參數(shù)兩者從外表上看是同一個(gè)東西奕剃,但實(shí)際上卻不是,所以他們兩者是可以任意變化的捐腿,也就是說(shuō)在內(nèi)部類(lèi)中我對(duì)屬性的改變并不會(huì)影響到外部的形參纵朋,而然這從程序員的角度來(lái)看這是不可行的,畢竟站在程序的角度來(lái)看這兩個(gè)根本就是同一個(gè)茄袖,如果內(nèi)部類(lèi)該變了操软,而外部方法的形參卻沒(méi)有改變這是難以理解和不可接受的,所以為了保持參數(shù)的一致性宪祥,就規(guī)定使用final來(lái)避免形參的不改變聂薪。

簡(jiǎn)單理解就是,拷貝引用品山,為了避免引用值發(fā)生改變,例如被外部類(lèi)的方法修改等烤低,而導(dǎo)致內(nèi)部類(lèi)得到的值不一致肘交,于是用final來(lái)讓該引用不可改變。

故如果定義了一個(gè)匿名內(nèi)部類(lèi)扑馁,并且希望它使用一個(gè)其外部定義的參數(shù)涯呻,那么編譯器會(huì)要求該參數(shù)引用是final的。

6.2匿名內(nèi)部類(lèi)小結(jié)

  • 匿名內(nèi)部類(lèi)是沒(méi)有訪問(wèn)修飾符的腻要。
  • 匿名內(nèi)部類(lèi)中不能存在任何的靜態(tài)成員變量和靜態(tài)方法复罐。
  • new 匿名內(nèi)部類(lèi),這個(gè)類(lèi)首先是要存在的雄家。如果我們將那個(gè)InnerClass接口注釋掉效诅,就會(huì)出現(xiàn)編譯出錯(cuò)。
  • 當(dāng)所在方法的形參需要被匿名內(nèi)部類(lèi)使用趟济,那么這個(gè)形參就必須為final乱投。
  • 匿名內(nèi)部類(lèi)創(chuàng)建一個(gè)接口的引用時(shí)是沒(méi)有構(gòu)造方法的。但是可以通過(guò)構(gòu)造代碼塊來(lái)模擬構(gòu)造器顷编,像下面這樣:
public A getA(){
    return new A(){
        int num = 0;
        String str;
        {
            str = "這是構(gòu)造代碼塊戚炫!";
            System.out.println("str 已經(jīng)被初始化!"); 
        }
    }
}

但是當(dāng)匿名內(nèi)部類(lèi)創(chuàng)建一個(gè)抽象類(lèi)或者實(shí)體類(lèi)的引用時(shí)媳纬,如果有必要双肤,是可以定義構(gòu)造函數(shù)的:

public class Outer{
    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner", "gz"); 
        System.out.println(inner.getName());
    }
    public Inner getInner(final String name, String city) { 
        return new Inner(name, city) { 
            private String nameStr = name; 
 
            public String getName() { 
                return nameStr; 
            } 
        }; 
    } 
}
abstract class Inner { 
    Inner(String name, String city) { 
        System.out.println(city); 
    } 
 
    abstract String getName(); 
} 
//注意這里的形參city施掏,由于它沒(méi)有被匿名內(nèi)部類(lèi)直接使用,而是被抽象類(lèi)Inner的構(gòu)造函數(shù)所使用茅糜,所以不必定義為final七芭。

匿名內(nèi)部類(lèi)不能是抽象的,它必須要實(shí)現(xiàn)繼承的類(lèi)或者實(shí)現(xiàn)的接口的所有抽象方法限匣。
事實(shí)上抖苦,創(chuàng)建匿名內(nèi)部類(lèi)要寫(xiě)的模板代碼太多了贪绘,java8中的lambda表達(dá)式能夠替代大部分的匿名類(lèi)跷坝,優(yōu)雅簡(jiǎn)潔代碼少,所以建議大家學(xué)習(xí)java8蹄葱,當(dāng)然峦筒,匿名內(nèi)部類(lèi)的知識(shí)還是要掌握的究西。

7.內(nèi)部類(lèi)的繼承

內(nèi)部類(lèi)的繼承,是指內(nèi)部類(lèi)被繼承物喷,普通類(lèi) extents 內(nèi)部類(lèi)卤材。而這時(shí)候代碼上要有點(diǎn)特別處理,具體看以下例子:

public class InheritInner extends WithInner.Inner{
    //InheritInner();是不能通過(guò)編譯的峦失,要加上形參
    InheritInner(WithInner wi){
        wi.super();
    }
    public static void main(String[] args){
        WithInner wi = new WithInner();
        InheritInner obj = new InheritInner(wi);
    }
}
class WithInner{
    class Inner{
    }
}

可以看到子類(lèi)的構(gòu)造函數(shù)里面要使用父類(lèi)的外部類(lèi)對(duì)象.super();而這個(gè)對(duì)象需要從外面創(chuàng)建并傳給形參扇丛。

8.多重繼承

內(nèi)部類(lèi)是除了接口外實(shí)現(xiàn)多重繼承的又一有利工具。
利用接口實(shí)現(xiàn)多重繼承我們都知道尉辑,就是一次性實(shí)現(xiàn)很多接口帆精。那么,如何利用內(nèi)部類(lèi)實(shí)現(xiàn)多重繼承呢隧魄?

//父親
public class Father {
    public int strong(){
        return 9;
    }
}
//母親
public class Mother {
    public int kind(){
        return 8;
    }
}
//兒子
public class Son {
    
 //   /**
     * 內(nèi)部類(lèi)繼承Father類(lèi)

    class Father_1 extends Father{
        public int strong(){
            return super.strong() + 1;
        }
    }
    
    class Mother_1 extends  Mother{
        public int kind(){
            return super.kind() - 2;
        }
    }
    
    public int getStrong(){
        return new Father_1().strong();
    }
    
    public int getKind(){
        return new Mother_1().kind();
    }
}
public class Test1 {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println("Son 的Strong:" + son.getStrong());
        System.out.println("Son 的kind:" + son.getKind());
    }
}

//輸出
//Son 的Strong:10
//Son 的kind:6

兒子繼承了父親卓练,變得比父親更加強(qiáng)壯,同時(shí)也繼承了母親购啄,只不過(guò)溫柔指數(shù)下降了襟企。這里定義了兩個(gè)內(nèi)部類(lèi),他們分別繼承父親Father類(lèi)狮含、母親類(lèi)Mother類(lèi)顽悼,且都可以非常自然地獲取各自父類(lèi)的行為,這是內(nèi)部類(lèi)一個(gè)重要的特性:內(nèi)部類(lèi)可以繼承一個(gè)與外部類(lèi)無(wú)關(guān)的類(lèi)几迄,保證了內(nèi)部類(lèi)的獨(dú)立性表蝙,正是基于這一點(diǎn),多重繼承才會(huì)成為可能乓旗。

9. 內(nèi)部類(lèi)的原理簡(jiǎn)析

上面說(shuō)過(guò)這樣兩點(diǎn):
(1) 在外部類(lèi)的作用范圍內(nèi)可以任意創(chuàng)建內(nèi)部類(lèi)對(duì)象府蛇,即使內(nèi)部類(lèi)是私有的(私有內(nèi)部類(lèi))。即內(nèi)部類(lèi)對(duì)包圍它的外部類(lèi)可見(jiàn)屿愚。
(2) 在內(nèi)部類(lèi)中可以訪問(wèn)其外部類(lèi)的所有域汇跨,即使是私有域务荆。即外部類(lèi)對(duì)內(nèi)部類(lèi)可見(jiàn)。

問(wèn)題來(lái)了:上面兩個(gè)特點(diǎn)到底如何辦到的呢穷遂??jī)?nèi)部類(lèi)的"內(nèi)部"到底發(fā)生了什么函匕?
其實(shí),內(nèi)部類(lèi)是Java編譯器一手操辦的蚪黑。虛擬機(jī)并不知道內(nèi)部類(lèi)與常規(guī)類(lèi)有什么不同盅惜。 編譯器是如何瞞住虛擬機(jī)的呢?
我們用javac命令編譯一下下面的代碼:

class Outer{   
       //外部類(lèi)私有數(shù)據(jù)域   
       private int data=0;   
       //內(nèi)部類(lèi)   
       class Inner{   
           void print(){   
                 //內(nèi)部類(lèi)訪問(wèn)外部私有數(shù)據(jù)域   
                 System.out.println(data);   
           }    
       }   
}  

對(duì)內(nèi)部類(lèi)進(jìn)行編譯后發(fā)現(xiàn)有兩個(gè)class文件:Outer.class 忌穿、和OuterInner.class 抒寂。 這說(shuō)明內(nèi)部類(lèi)Inner仍然被編譯成一個(gè)獨(dú)立的類(lèi)(OuterInner.class),而不是Outer類(lèi)的某一個(gè)域掠剑。 虛擬機(jī)運(yùn)行的時(shí)候屈芜,也是把Inner作為一種常規(guī)類(lèi)來(lái)處理的。
但問(wèn)題又來(lái)了朴译,即然是兩個(gè)常規(guī)類(lèi)井佑,為什么他們之間可以互相訪問(wèn)私有域那(最開(kāi)始提到的兩個(gè)內(nèi)部類(lèi)特點(diǎn))?這就要問(wèn)問(wèn)編譯器到底把這兩個(gè)類(lèi)編譯成什么東西了眠寿。
我們利用reflect反射機(jī)制來(lái)探查了一下內(nèi)部類(lèi)編譯后的情況:

//反編譯后的Outer$Inner
class Outer$Inner{   
        Outer$Inner(Outer,Outer$Inner);  //包可見(jiàn)構(gòu)造器   
        private Outer$Inner(Outer);   //私有構(gòu)造器將設(shè)置this$0域   
        final Outer this$0;   //外部類(lèi)實(shí)例域this$0  
} 

好了躬翁,現(xiàn)在我們可以解釋上面的第一個(gè)內(nèi)部類(lèi)特點(diǎn)了: 為什么外部類(lèi)可以創(chuàng)建內(nèi)部類(lèi)的對(duì)象?并且內(nèi)部類(lèi)能夠方便的引用到外部類(lèi)對(duì)象?
首先編譯器將外盯拱、內(nèi)部類(lèi)編譯后放在同一個(gè)包中盒发。在內(nèi)部類(lèi)中附加一個(gè)包可見(jiàn)構(gòu)造器。這樣坟乾, 虛擬機(jī)運(yùn)行Outer類(lèi)中Inner in=new Inner(); 實(shí)際上調(diào)用的是包可見(jiàn)構(gòu)造:

new Outer$Inner(this,null)迹辐。

因此即使是private內(nèi)部類(lèi)蝶防,也會(huì)通過(guò)隱含的包可見(jiàn)構(gòu)造器成功的獲得私有內(nèi)部類(lèi)的構(gòu)造權(quán)限甚侣。
再者,OuterInner類(lèi)中有一個(gè)指向外部類(lèi)Outer的引用this0间学,那么通過(guò)這個(gè)引用就可以方便的得到外部類(lèi)對(duì)象中可見(jiàn)成員殷费。
但是Outer類(lèi)中的private成員是如何訪問(wèn)到的呢?這就要看看下面Outer.class文件中的秘密了低葫。

class Outer{   
    static int access$0(Outer);  //靜態(tài)方法详羡,返回值是外部類(lèi)私有域 data 的值。   
}  

現(xiàn)在可以解釋第二個(gè)特點(diǎn)了:為什么內(nèi)部類(lèi)可以引用外部類(lèi)的私有域嘿悬?
原因的關(guān)鍵就在編譯器在外圍類(lèi)中添加了靜態(tài)方法access0实柠。 它將返回值作為參數(shù)傳遞給他的對(duì)象域data。 這樣內(nèi)部類(lèi)Inner中的打印語(yǔ)句:System.out.println(data); 實(shí)際上運(yùn)行的時(shí)候調(diào)用的是:System.out.println(this0.access$0(Outer));
總結(jié)一下編譯器對(duì)類(lèi)中內(nèi)部類(lèi)做的手腳吧:
(1) 在內(nèi)部類(lèi)中偷偷摸摸的創(chuàng)建了包可見(jiàn)構(gòu)造器善涨,從而使外部類(lèi)獲得了創(chuàng)建權(quán)限窒盐。
(2) 在外部類(lèi)中偷偷摸摸的創(chuàng)建了訪問(wèn)私有變量的靜態(tài)方法草则,從而 使 內(nèi)部類(lèi)獲得了訪問(wèn)權(quán)限。這樣蟹漓,類(lèi)中定義的內(nèi)部類(lèi)無(wú)論私有炕横,公有,
靜態(tài)都可以被包圍它的外部類(lèi)所訪問(wèn)葡粒。

內(nèi)部類(lèi)我還是一知半解份殿,以后還是要回過(guò)頭來(lái)結(jié)合實(shí)例詳細(xì)了解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嗽交,一起剝皮案震驚了整個(gè)濱河市卿嘲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轮纫,老刑警劉巖腔寡,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異掌唾,居然都是意外死亡放前,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)糯彬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凭语,“玉大人,你說(shuō)我怎么就攤上這事撩扒∷迫樱” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵搓谆,是天一觀的道長(zhǎng)炒辉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)泉手,這世上最難降的妖魔是什么黔寇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮斩萌,結(jié)果婚禮上缝裤,老公的妹妹穿的比我還像新娘。我一直安慰自己颊郎,他們只是感情好憋飞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著姆吭,像睡著了一般榛做。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天检眯,我揣著相機(jī)與錄音升敲,去河邊找鬼。 笑死轰传,一個(gè)胖子當(dāng)著我的面吹牛驴党,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播获茬,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼港庄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恕曲?” 一聲冷哼從身側(cè)響起鹏氧,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎佩谣,沒(méi)想到半個(gè)月后把还,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茸俭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年吊履,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片调鬓。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡艇炎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腾窝,到底是詐尸還是另有隱情缀踪,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布虹脯,位于F島的核電站驴娃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏循集。R本人自食惡果不足惜唇敞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望暇榴。 院中可真熱鬧厚棵,春花似錦蕉世、人聲如沸蔼紧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奸例。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間查吊,已是汗流浹背谐区。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逻卖,地道東北人宋列。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像评也,于是被迫代替她去往敵國(guó)和親炼杖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 注意注意5脸佟@ば啊!前排提示7B啤MХ摹!本篇文章過(guò)長(zhǎng)邮弹,最好收藏下來(lái)慢慢看黔衡,如果你之前對(duì)內(nèi)部類(lèi)不是很熟悉,一次性看完腌乡,大概你會(huì)懵...
    Sharember閱讀 819評(píng)論 0 10
  • Java 內(nèi)部類(lèi) 分四種:成員內(nèi)部類(lèi)员帮、局部?jī)?nèi)部類(lèi)、靜態(tài)內(nèi)部類(lèi)和匿名內(nèi)部類(lèi)导饲。 1捞高、成員內(nèi)部類(lèi): 即作為外部類(lèi)的一個(gè)成...
    ikaroskun閱讀 1,219評(píng)論 0 13
  • 一袋毙、繼承 當(dāng)兩個(gè)事物之間存在一定的所屬關(guān)系型檀,即就像孩子從父母那里得到遺傳基因一樣,當(dāng)然听盖,java要遺傳的更完美胀溺,這...
    玉圣閱讀 1,046評(píng)論 0 2
  • 整理來(lái)自互聯(lián)網(wǎng) 1,JDK:Java Development Kit皆看,java的開(kāi)發(fā)和運(yùn)行環(huán)境仓坞,java的開(kāi)發(fā)工具...
    Ncompass閱讀 1,534評(píng)論 0 6
  • 一:java概述: 1,JDK:Java Development Kit腰吟,java的開(kāi)發(fā)和運(yùn)行環(huán)境无埃,java的開(kāi)發(fā)...
    慕容小偉閱讀 1,766評(píng)論 0 10