Java基礎(chǔ)-面向?qū)ο?內(nèi)部類

Java工程師知識(shí)樹(shù) / Java基礎(chǔ)


Java內(nèi)部類

概念:

Java 類中不僅可以定義變量和方法灾挨,還可以定義類邑退,這樣定義在類內(nèi)部的類就被稱為內(nèi)部類。

內(nèi)部類的實(shí)現(xiàn)過(guò)程

通過(guò)反編譯內(nèi)部類的字節(jié)碼劳澄, 分析之后主要是通過(guò)以下幾步做到的:

  1. 編譯器自動(dòng)為內(nèi)部類添加一個(gè)成員變量地技, 這個(gè)成員變量的類型和外部類的類型相同, 這個(gè)成員變量就是指向外部類對(duì)象的引用秒拔;
  2. 編譯器自動(dòng)為內(nèi)部類的構(gòu)造方法添加一個(gè)參數(shù)莫矗, 參數(shù)的類型是外部類的類型, 在構(gòu)造方法內(nèi)部使用這個(gè)參數(shù)為1中添加的成員變量賦值溯警;
  3. 在調(diào)用內(nèi)部類的構(gòu)造函數(shù)初始化內(nèi)部類對(duì)象時(shí)趣苏, 會(huì)默認(rèn)傳入外部類的引用。
public class Outer {
    private int m = 1;
    public class Inner {
        private void test() {
            //訪問(wèn)外部類private成員
            System.out.println(m);
        }
    }
}
//編譯梯轻,會(huì)發(fā)現(xiàn)會(huì)在編譯目標(biāo)目錄生成兩個(gè).class文件:Outer.class和Outer$Inner.class。
//將Outer$Inner.class放入IDEA中打開(kāi)尽棕,會(huì)自動(dòng)反編譯喳挑,查看結(jié)果:
public class Outer$Inner {
    public Outer$Inner(Outer this$0) {
        this.this$0 = this$0;
    }

    private void test() {
        System.out.println(Outer.access$000(this.this$0));
    }
}

為什么需要內(nèi)部類

其主要原因有以下幾點(diǎn):

  • 內(nèi)部類方法可以訪問(wèn)該類定義所在的作用域的數(shù)據(jù),包括私有的數(shù)據(jù)
  • 內(nèi)部類可以對(duì)同一個(gè)包中的其他類隱藏起來(lái),一般的非內(nèi)部類滔悉,是不允許有 private 與protected權(quán)限的伊诵,但內(nèi)部類可以可是實(shí)現(xiàn)多重繼承
  • 當(dāng)想要定義一個(gè)回調(diào)函數(shù)且不想編寫(xiě)大量代碼時(shí),使用匿名內(nèi)部類比較便捷回官。

使用內(nèi)部類最吸引人的原因是:

每個(gè)內(nèi)部類都能獨(dú)立地繼承自一個(gè)(接口的)實(shí)現(xiàn)曹宴,所以無(wú)論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類都沒(méi)有影響歉提。大家都知道Java只能繼承一個(gè)類笛坦,它的多重繼承在我們沒(méi)有學(xué)習(xí)內(nèi)部類之前是用接口來(lái)實(shí)現(xiàn)的。但使用接口有時(shí)候有很多不方便的地方苔巨。比如我們實(shí)現(xiàn)一個(gè)接口就必須實(shí)現(xiàn)它里面的所有方法版扩。而有了內(nèi)部類就不一樣了。它可以使我們的類繼承多個(gè)具體類或抽象類侄泽。

內(nèi)部類的類型

根據(jù)定義的方式不同礁芦,內(nèi)部類分為靜態(tài)內(nèi)部類,成員內(nèi)部類挣郭,局部?jī)?nèi)部類殊鞭,匿名內(nèi)部類四種。

靜態(tài)內(nèi)部類:

定義在類內(nèi)部的靜態(tài)類非洲,就是靜態(tài)內(nèi)部類未状。 用static修飾的成員內(nèi)部類又稱為嵌套類俯画。

public class Out {
    private static int a;
    private int b;

    public static class Inner {
        public void print() {
            System.out.println(a);
        }
    }
}

解釋:

  1. 靜態(tài)內(nèi)部類可以訪問(wèn)外部類所有的靜態(tài)變量和方法,即使是 private 的也一樣娩践。

  2. 靜態(tài)內(nèi)部類和一般類一致活翩,可以定義靜態(tài)變量、方法翻伺,構(gòu)造方法等材泄。

  3. 其它類使用靜態(tài)內(nèi)部類需要使用"外部類.靜態(tài)內(nèi)部類"方式,如下所示:

    Out.Inner inner = new Out.Inner();
    inner.print(); 
    
  1. Java集合類HashMap內(nèi)部就有一個(gè)靜態(tài)內(nèi)部類Entry吨岭。Entry是HashMap存放元素的抽象拉宗, HashMap 內(nèi)部維護(hù) Entry 數(shù)組用了存放元素,但是 Entry 對(duì)使用者是透明的辣辫。像這種和外部類關(guān)系密切的旦事,且不依賴外部類實(shí)例的,都可以使用靜態(tài)內(nèi)部類急灭。
成員內(nèi)部類

定義在類內(nèi)部的非靜態(tài)類姐浮,就是成員內(nèi)部類。

成員內(nèi)部類不能定義靜態(tài)方法和變量(final 修飾的除外)葬馋。這是因?yàn)槌蓡T內(nèi)部類是非靜態(tài)的卖鲤,類初始化的時(shí)候先初始化靜態(tài)成員,如果允許成員內(nèi)部類定義靜態(tài)變量畴嘶,那么成員內(nèi)部類的靜態(tài)變量初始化順序是有歧義的蛋逾。

public class Out {
    private static int a;
    private int b;

    public class Inner {
        public void print() {
            System.out.println(a);
            System.out.println(b);
        }
    }
} 
局部?jī)?nèi)部類(定義在方法中的類)

定義在方法中的類,就是局部類窗悯。

如果一個(gè)類只在某個(gè)方法中使用区匣,則可以考慮使用局部類。

public class Out {
    private static int a;
    private int b;

    public void test(final int c) {
        final int d = 1;
        class Inner {
            public void print() {
                System.out.println(c);
            }
        }
    }
}
匿名內(nèi)部類(要繼承一個(gè)父類或者實(shí)現(xiàn)一個(gè)接口蒋院、直接使用 new 來(lái)生成一個(gè)對(duì)象的引用)

匿名內(nèi)部類我們必須要繼承一個(gè)父類或者實(shí)現(xiàn)一個(gè)接口亏钩,當(dāng)然也僅能只繼承一個(gè)父類或者實(shí)現(xiàn)一個(gè)接口。

同時(shí)它也是沒(méi)有class關(guān)鍵字悦污,這是因?yàn)槟涿麅?nèi)部類是直接使用new來(lái)生成一個(gè)對(duì)象的引用铸屉。

public abstract class Bird {

    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    Bird(String name) {
        this.name = name;
    }
    public abstract int fly();
}
public class Test {
    
    public void test(Bird bird) {
        System.out.println(bird.getName() + "能夠飛 " + bird.fly() + "米");
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.test(new Bird("大雁") {
            public int fly() {
                return 10000;
            }
        });
        test.test(new Bird("麻雀") {
            public int fly() {
                return 1000;
            }
        });
    }
} 

怎么正確的使用內(nèi)部類

1.注意內(nèi)存泄露

《Effective Java》第二十四小節(jié)明確提出過(guò)。優(yōu)先使用靜態(tài)內(nèi)部類切端。這是為什么呢彻坛?
由上面的分析我們可以知道,除了嵌套類,其他的內(nèi)部類都隱式包含了外部類對(duì)象昌屉。這便是Java內(nèi)存泄露的源頭钙蒙。看代碼:

定義Outer:

public class Outer{

    public  List getList(String item) {

        return new ArrayList() {
            {
                add(item);
            }
        };
    }
}

使用Outer:

public class Test{

   public static List getOutersList(){

    Outer outer=new Outer();
    //do something
    List list=outer.getList("test");

    return list;    
   }
   public static void main(String[] args){
       List list=getOutersList();


      //do something with list
   }

}

相信這樣的代碼一定有同學(xué)寫(xiě)出來(lái)间驮,這涉及到一個(gè)習(xí)慣的問(wèn)題:

不涉及到類成員方法和成員變量的方法躬厌,最好定義為static

我們先研究上面的代碼,最大的問(wèn)題便是帶來(lái)的內(nèi)存泄露

在使用過(guò)程中竞帽,我們定義Outer對(duì)象完成一系列的動(dòng)作

  • 使用outer得到了一個(gè)ArraList對(duì)象
  • 將ArrayList作為結(jié)果返回出去扛施。

正常來(lái)說(shuō),在getOutersList方法中屹篓,我們new出來(lái)了兩個(gè)對(duì)象:outer和list疙渣,而在離開(kāi)此方法時(shí),我們只將list對(duì)象的引用傳遞出去,outer的引用隨著方法棧的退出而被銷毀堆巧。按道理來(lái)說(shuō)妄荔,outer對(duì)象此時(shí)應(yīng)該沒(méi)有作用了,也應(yīng)該在下一次內(nèi)存回收中被銷毀谍肤。

然而,事實(shí)并不是這樣啦租。按上面所說(shuō)的,新建的list對(duì)象是默認(rèn)包含對(duì)outer對(duì)象的引用的荒揣,因此只要list不被銷毀篷角,outer對(duì)象將會(huì)一直存在,然而我們并不需要outer對(duì)象系任,這便是內(nèi)存泄露内地。

怎么避免這種情況呢?

很簡(jiǎn)單:不涉及到類成員方法和成員變量的方法赋除,最好定義為static

public class Outer{

    public static List getList(String item) {

        return new ArrayList() {
            {
                add(item);
            }
        };
    }
}

這樣定義出來(lái)的類便是嵌套類+繼承,并不包含對(duì)外部類的引用非凌。

2.應(yīng)用于只實(shí)現(xiàn)一個(gè)接口的實(shí)現(xiàn)類
  • 優(yōu)雅工廠方法模式

我們可以看到举农,在工廠方法模式中,每個(gè)實(shí)現(xiàn)都會(huì)需要實(shí)現(xiàn)一個(gè)Fractory來(lái)實(shí)現(xiàn)產(chǎn)生對(duì)象的接口敞嗡,而這樣接口其實(shí)和原本的類關(guān)聯(lián)性很大的颁糟,因此我們可以將Fractory定義在具體的類中,作為內(nèi)部類存在

  • 簡(jiǎn)單的實(shí)現(xiàn)接口
       new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("test");
           }
       }

       ).start();
    }

盡量不要直接使用Thread喉悴,這里只做演示使用
Java 8 的話建議使用lambda代替此類應(yīng)用

  • 同時(shí)實(shí)現(xiàn)多個(gè)接口
public class imple{

    public static Eat getDogEat(){
        return new EatDog();
    }

    public static Eat getCatEat(){
        return new EatCat();
    }

    private static class EatDog implements Eat {
        @Override
        public void eat() {
            System.out.println("dog eat");
        }
    }
    private static class EatCat implements Eat{
        @Override
        public void eat() {
            System.out.println("cat eat");
        }
    }
}
3.優(yōu)雅的單例類
public class Imple {

    public static Imple getInstance(){
        return ImpleHolder.INSTANCE;
    }


    private static class ImpleHolder{
        private static final Imple INSTANCE=new Imple();
    }
}
4.反序列化JSON接受的JavaBean

有時(shí)候需要反序列化嵌套JSON

{
    "student":{
        "name":"",
        "age":""
    }
}

類似這種棱貌。我們可以直接定義嵌套類進(jìn)行反序列化

public JsonStr{

    private Student student;

    public static Student{
        private String name;
        private String age;

        //getter & setter
    }

    //getter & setter
}

但是注意,這里應(yīng)該使用嵌套類箕肃,因?yàn)槲覀儾恍枰屯獠款愡M(jìn)行數(shù)據(jù)交換婚脱。

核心思想:

  • 嵌套類能夠訪問(wèn)外部類的構(gòu)造函數(shù)
  • 將第一次訪問(wèn)內(nèi)部類放在方法中,這樣只有調(diào)用這個(gè)方法的時(shí)候才會(huì)第一次訪問(wèn)內(nèi)部類,實(shí)現(xiàn)了懶加載

總結(jié)

內(nèi)部類的理解可以按照方法來(lái)理解障贸,但是內(nèi)部類很多特性都必須剝開(kāi)語(yǔ)法糖和明白為什么需要這么做才能完全理解错森,明白內(nèi)部類的所有特性才能更好使用內(nèi)部類,在內(nèi)部類的使用過(guò)程中篮洁,一定記咨:能使用嵌套類就使用嵌套類,如果內(nèi)部類需要和外部類聯(lián)系袁波,才使用內(nèi)部類瓦阐。最后不涉及到類成員方法和成員變量的方法,最好定義為static可以防止內(nèi)部類內(nèi)存泄露篷牌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末睡蟋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子娃磺,更是在濱河造成了極大的恐慌薄湿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偷卧,死亡現(xiàn)場(chǎng)離奇詭異豺瘤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)听诸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)坐求,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人晌梨,你說(shuō)我怎么就攤上這事桥嗤。” “怎么了仔蝌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵泛领,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我敛惊,道長(zhǎng)渊鞋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任瞧挤,我火速辦了婚禮锡宋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘特恬。我一直安慰自己执俩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布癌刽。 她就那樣靜靜地躺著役首,像睡著了一般尝丐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宋税,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天摊崭,我揣著相機(jī)與錄音,去河邊找鬼杰赛。 笑死呢簸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的乏屯。 我是一名探鬼主播根时,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辰晕!你這毒婦竟也來(lái)了蛤迎?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤含友,失蹤者是張志新(化名)和其女友劉穎替裆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體窘问,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辆童,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惠赫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片把鉴。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖儿咱,靈堂內(nèi)的尸體忽然破棺而出庭砍,到底是詐尸還是另有隱情,我是刑警寧澤混埠,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布怠缸,位于F島的核電站,受9級(jí)特大地震影響钳宪,放射性物質(zhì)發(fā)生泄漏凯旭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一使套、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鞠柄,春花似錦侦高、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)计螺。三九已至,卻和暖如春瞧壮,著一層夾襖步出監(jiān)牢的瞬間登馒,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工咆槽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陈轿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓秦忿,卻偏偏與公主長(zhǎng)得像麦射,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子灯谣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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