淺談Java內(nèi)部類

由于Java核心技術(shù)上的例子很不錯,本篇內(nèi)部類博客部分代碼以Java核心技術(shù)上的例子進行講解侧馅。

內(nèi)部類總覽

內(nèi)部類分為四種,分別是:

  • 一般的內(nèi)部類(與方法在一級上的)
  • 局部內(nèi)部類(在方法中的類)
  • 匿名內(nèi)部類(沒有類名的類)
  • 靜態(tài)內(nèi)部類(也稱為嵌套類)

一般的內(nèi)部類

下面是一個一般內(nèi)部類的代碼展示:

public class InnerClassTest {
    public static void main(String[] args) {
        TalkingClock clock = new TalkingClock(1000, true);
        clock.start();
        JOptionPane.showMessageDialog(null, "Quit Program?");
        System.exit(0);
    }
}

class TalkingClock {

    private int interval;

    private boolean beep;

    public TalkingClock(int interval, boolean beep) {
        this.interval = interval;
        this.beep = beep;
    }

    public void start() {

        ActionListener listener = new TimePrinter();
        Timer timer = new Timer(interval, listener);
        timer.start();
    }

    //一般的內(nèi)部類
    public class TimePrinter implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("At the tone, the time is " + new Date());
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    }
}

從上面我們可以看到在TimePrinter這個內(nèi)部類中的actionPerformed方法中引用了外部類TalkingClock的interval和beep域呐萌。所以說我們從這里可以看出一般的內(nèi)部類的一個特點就是可以訪問自身的數(shù)據(jù)域馁痴,也可以訪問創(chuàng)建它的外部類對象的數(shù)據(jù)域。

那么為了讓內(nèi)部類有這樣的特性肺孤,它是怎么做到的呢弥搞?其實,在內(nèi)部類中總有一個隱式的引用指向著創(chuàng)建它的外部類的對象渠旁。就像下面的一樣:

光這么說可能你理解并不會太深刻,那么現(xiàn)在我們走向代碼里面去船逮。先來一張圖顾腊,再來一一解釋吧!

當然我是在生成字節(jié)碼的目錄下挖胃,然后了解Linux的童鞋都知道ls是列出當然目錄下的可見的文件或者文件夾的命令杂靶。然后這些字節(jié)碼有點多,但別去管其它的其它是一些無關(guān)緊要的類的字節(jié)碼酱鸭。我們直接看到TalkingClock.class和TalkingClock$TimePrinter.class吗垮,他們分別就是上面外部類和內(nèi)部類的字節(jié)碼啦!看到這是是不是感覺被騙了凹髓,emmm…內(nèi)部類其實就是編譯器的一個語法烁登,在編譯后的話沒有啥字節(jié)碼了,就是和泛型蔚舀、foreach等一樣的是一層編譯器語法糖饵沧。內(nèi)部類會被單獨的編譯出來锨络,然后通過$來連接外部類和內(nèi)部類成為內(nèi)部類的類名。

然后在后面我們通過javap -private <字節(jié)碼>(這里我們看第一個javap得到的東西)用來顯示所有類和成員狼牺。我看的final TalkingClock this$0;羡儿。this$0這個變量是TalkingClock類型的,沒錯是钥,其實這就是我們上面給出的圖中的outer那個變量掠归。這樣一切都明了了。之所以在內(nèi)部類中能夠去引用外部類的數(shù)據(jù)域就是因為我們的內(nèi)部類只用隱藏著一個外部類的引用悄泥。

然后虏冻,還想說的就是我們這樣一般的內(nèi)部類,可以是私有類码泞,但是常規(guī)類(也就是我們的外部類一樣的類)只能是包可見性或公有可見性兄旬。然后,也就是如果我們的內(nèi)部類對于其它類可見的時候余寥,我也可以來進行內(nèi)部類的創(chuàng)建领铐。但是得注意一點的是我們?nèi)绻谄渌愡M行一般內(nèi)部類的創(chuàng)建的時候,我們要借助于一個外部類對象進行創(chuàng)建宋舷。也就是像下面一樣進行創(chuàng)建绪撵。

TalkingClock clock = new TalkingClock(1000,true);

TalkingClock.TimePrinter printer = clock.new TimePrinter();

雖然,基于Java 8的Java核心技術(shù)書上在說內(nèi)部類中聲明的所有靜態(tài)域都必須是final以及可以聲明靜態(tài)方法祝蝠。但是筆者在Java 9上發(fā)現(xiàn)這樣的一般內(nèi)部類是不能進行靜態(tài)域或者是靜態(tài)方法的聲明的音诈,要聲明只能讓內(nèi)部類變成靜態(tài)內(nèi)部類才行。

通過前面的代碼我們可以發(fā)現(xiàn)一般的內(nèi)部類是能夠去訪問對應外部類的私有域的绎狭。它是怎么做到的呢税肪!我們看到第二個javap得到的內(nèi)容。我們發(fā)現(xiàn)其中有一個static boolean access$000(TalkingClock)的方法遭顶。

我們在內(nèi)部類的用到了私有的beep芜飘,因此在外部類中這個自動生成的靜態(tài)方法來實現(xiàn)私有beep的訪問的。當然由于編譯器的不同方法名可能會不同蹦狂,如:access$0誓篱。

局部內(nèi)部類

為了后面好分析,先來看一波代碼吧:

public class InnerClassTest {
    public static void main(String[] args) {
        TalkingClock clock = new TalkingClock();
        clock.start(1000, true);
        JOptionPane.showMessageDialog(null, "Quit Program?");
        System.exit(0);
    }
}

class TalkingClock {

    public void start(int interval,boolean beep) {

        //局部內(nèi)部類
        class TimePrinter implements ActionListener{

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("At the tone, the time is " + new Date());
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
        }

        ActionListener listener = new TimePrinter();
        Timer timer = new Timer(interval, listener);
        timer.start();
    }

}

從代碼我們可以看到凯楔,局部內(nèi)部類就是將內(nèi)部類放在了外部類的一個方法內(nèi)窜骄。然后得注意一點的就是局部內(nèi)部類不能夠用public或者是private訪問說明符進行聲明。它的作用于限定在聲明這個局部內(nèi)部類的塊中摆屯。因此這也是它的一個優(yōu)勢邻遏,那就是這個內(nèi)部類對外面的世界是完全隱藏的

在局部內(nèi)部類中實現(xiàn)外部類私有變量的訪問的方案是和上面一般的內(nèi)部類訪問私有變量是一樣的。但是如果一個局部內(nèi)部類對一個局部變量的訪問的話,那么這個局部內(nèi)部類中就會存儲一個局部變量的副本党远。就像下面一樣:

那么上面代碼的整個流程我們來解釋一下:

  • 調(diào)用start方法
  • 調(diào)用內(nèi)部類TimePrinter的構(gòu)造器削解,初始化listener變量
  • 將listener傳給Timer構(gòu)造器,定時器開始計時沟娱,start方法結(jié)束氛驮。然后此時,start方法結(jié)束济似,beep參數(shù)不復存在
  • 然后矫废,actionPerformed執(zhí)行if(beep)...

我們可以看到在start方法結(jié)束后,變量beep就不存在了砰蠢。

Java 8之前的話蓖扑,必須把局部內(nèi)部類定義為final的局部變量才行的。比如上面的start方法就會變成下面的樣子:

public void start(int interval,final boolean beep){
    ...
}

匿名內(nèi)部類

咱們依然先來看看代碼:

public class InnerClassTest {
    public static void main(String[] args) {
        TalkingClock clock = new TalkingClock();
        clock.start(1000, true);
        JOptionPane.showMessageDialog(null, "Quit Program?");
        System.exit(0);
    }
}


class TalkingClock {

    public void start(int interval, boolean beep) {

        //匿名內(nèi)部類
        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("At the tone, the time is " + new Date());
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
        };
        Timer timer = new Timer(interval, listener);
        timer.start();
    }
}

有上面的代碼我們可以看到台舱,匿名內(nèi)部類就是只用創(chuàng)建一個對象律杠,而不需要對其命名(就是沒有類名)的類。從形式上來說就是一個構(gòu)造參數(shù)的閉小括號后面跟一個大括號竞惋,就是一個匿名內(nèi)部類柜去。其中大括號就是普通的類后面的大括號,里面可以寫自己的方法也可以重寫父類或者是接口中的方法拆宛。

由于構(gòu)造器的名字要和類名相同嗓奢,但是匿名內(nèi)部類沒有類名。所以浑厚,匿名類不能有構(gòu)造器股耽。

像上面ActionListener這樣只有一個方法必須在實現(xiàn)它的類中進行重寫的接口,我們叫做函數(shù)式接口钳幅。實現(xiàn)這樣的接口的匿名內(nèi)部類物蝙,我們可以用lambada表達式來進行代替。就像下面這樣:

ActionListener listener = e -> {
    System.out.println("At the tone, the time is " + new Date());
    if (beep) Toolkit.getDefaultToolkit().beep();
};

對于匿名內(nèi)部類敢艰,我們有一個雙括號初始化的技巧诬乞。比如像一個方法需要傳一個數(shù)組列表,但是這個數(shù)組列表無需再被用到盖矫,那么我們就可以用這個技巧。現(xiàn)在击奶,我們來看看代碼:

// 好久沒玩LOL辈双,邀請一波好友來玩下!
public class Test {
    public static void main(String[] args) {
        //沒有使用匿名內(nèi)部類
        ArrayList<String> friends = new ArrayList();
        friends.add("anriku");
        friends.add("zzia");
        friends.add("zxZhu");
        inviteFriendToPlayLOL(friends);


        //使用匿名內(nèi)部類柜砾,并用雙重括號初始化
        inviteFriendToPlayLOL(new ArrayList<>() {{
            add("Jay");
            add("acemurder");
            add("Mike");
        }});
    }

    private static void inviteFriendToPlayLOL(List<String> friends) {
        System.out.println(friends);
    }
}

大括號中的大括號湃望,叫做構(gòu)造塊構(gòu)造塊會在每一個對象構(gòu)造的時候進行調(diào)用。與靜態(tài)代碼塊不同的就是靜態(tài)代碼塊只會在一個類被加載的時候進行調(diào)用证芭。調(diào)用順序是這樣的:靜態(tài)塊>構(gòu)造塊>構(gòu)造方法瞳浦。

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

如果我們的內(nèi)部類只是想完全的隱藏在一個類之中,并不需要這個內(nèi)部類與外面的類打交道废士。那么我們可以將內(nèi)部類聲明為static(也只有內(nèi)部類能夠被static修飾)叫潦,這樣的話內(nèi)部類會不會持有外部類的引用。這樣的話官硝,內(nèi)部類只能夠使用外部類的靜態(tài)變量或者是靜態(tài)方法了矗蕊。不能引用外部類的實例域或者是方法了。

現(xiàn)在我們來舉個栗子:

public class InnerClassTest {
    public static void main(String[] args) {
        double[] d = new double[20];
        for (int i = 0;i < d.length;i++){
            d[i] = 100*Math.random();
            ArrayAlg.Pair p = ArrayAlg.minmax(d);
            System.out.println("min = " + p.getFirst());
            System.out.println("max = " + p.getSecond());
        }
    }
}

class ArrayAlg{

    //靜態(tài)內(nèi)部類
    public static class Pair{
        private double first;

        private double second;

        Pair(double first, double second) {
            this.first = first;
            this.second = second;
        }

        public double getFirst() {
            return first;
        }

        public double getSecond() {
            return second;
        }
    }

    public static Pair minmax(double[] values){
        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;

        for (double v:values){
            if (min > v) min = v;
            if (max < v) max = v;
        }
        return new Pair(min,max);
    }
}

我們?yōu)槭裁匆@樣寫呢氢架?其中的過程就是:

  • 我們需要一個minmax用來將一個數(shù)組中的最大最小值比較出來傻咖。由于兩個方法的話就要進行兩次的遍歷比較。于是就想到用一個方法岖研,但是要同時返回最大和最小值卿操,那么我們就用Pair類來將兩個值連在一起。
  • 但是如果單獨做一個類的話孙援,有兩個缺點:
    • 以Pair為類名的類太多了
    • 這個Pair類實際上只在ArrayAlg類中使用到
  • 于是我們就讓其作為一個內(nèi)部類
  • 再由于這個內(nèi)部類與外部沒有什么干系(也就是內(nèi)部類完全不需要訪問外圍對象)害淤,于是就讓其作為一個靜態(tài)內(nèi)部類

那么靜態(tài)內(nèi)部類是不是真的沒有去引用外部類對象呢,我們依然用javap命令去看一看:


從上面我們可以看到在這個內(nèi)部類中我們沒有看到之前外部類對象的引用赃磨。所以說這是真沒關(guān)系的筝家。

然后需要注意的一點就是,當外部類進行加載的時候邻辉,并且外部類沒有用到靜態(tài)內(nèi)部類的時候溪王,靜態(tài)內(nèi)部類是不會加載的。我們可以來看下測試代碼值骇。

//一個測試類莹菱,其中包括了一個靜態(tài)內(nèi)部類
public class StaticClassTest {

    public static void test(){
        System.out.println("Test");
    }

    static class InnerStaticClass{
        static {
            System.out.println("This is static block of InnerStaticClass");
        }
    }
}

public class Main{
    public static void main(String[] args) {
        StaticClassTest.test();
    }
}

咱們來看一下代碼運行的結(jié)果:


沒錯吧,運行結(jié)果中沒有執(zhí)行靜態(tài)內(nèi)部類中的靜態(tài)代碼塊所需打印的東西吱瘩。

總結(jié)

今天道伟,我們學習了內(nèi)部類相關(guān)的東西。雖然還是有點復雜使碾,但是通過我們一步一步的分析蜜徽,我們揭開了其中神秘的東西。對其了解也是更上一層樓了吧票摇!

其中拘鞋,一般的內(nèi)部類局部內(nèi)部類矢门、匿名內(nèi)部類都會包含一個創(chuàng)建它的外部類的對象的引用盆色。但是靜態(tài)內(nèi)部類不會持有這么一個變量灰蛙。

參考

  • Java核心技術(shù) 卷一
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市隔躲,隨后出現(xiàn)的幾起案子摩梧,更是在濱河造成了極大的恐慌,老刑警劉巖宣旱,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仅父,死亡現(xiàn)場離奇詭異,居然都是意外死亡响鹃,警方通過查閱死者的電腦和手機驾霜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來买置,“玉大人粪糙,你說我怎么就攤上這事》尴睿” “怎么了蓉冈?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長轩触。 經(jīng)常有香客問我寞酿,道長,這世上最難降的妖魔是什么脱柱? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任伐弹,我火速辦了婚禮,結(jié)果婚禮上榨为,老公的妹妹穿的比我還像新娘惨好。我一直安慰自己,他們只是感情好随闺,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布日川。 她就那樣靜靜地躺著,像睡著了一般矩乐。 火紅的嫁衣襯著肌膚如雪龄句。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天散罕,我揣著相機與錄音分歇,去河邊找鬼。 笑死欧漱,一個胖子當著我的面吹牛职抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播硫椰,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼繁调,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了靶草?” 一聲冷哼從身側(cè)響起蹄胰,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奕翔,沒想到半個月后裕寨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡派继,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年宾袜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驾窟。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡庆猫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绅络,到底是詐尸還是另有隱情月培,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布恩急,位于F島的核電站杉畜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏衷恭。R本人自食惡果不足惜此叠,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望随珠。 院中可真熱鬧灭袁,春花似錦、人聲如沸牙丽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烤芦。三九已至举娩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間构罗,已是汗流浹背铜涉。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遂唧,地道東北人芙代。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像盖彭,于是被迫代替她去往敵國和親纹烹。 傳聞我的和親對象是個殘疾皇子页滚,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

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

  • 如果文章對你有所幫助,請點喜歡并關(guān)注铺呵,這將是我最大的動力裹驰,謝謝 為什么要有內(nèi)部類 1.內(nèi)部類是為了更好的封裝,把內(nèi)...
    光哥很霸氣閱讀 9,718評論 -1 31
  • 局部內(nèi)部類 局部內(nèi)部類是內(nèi)部類的第二種形式片挂,它讓內(nèi)部類的“隱藏”得更深一層——寫在外部類的方法內(nèi)部幻林,而不是處于和外...
    java部落閱讀 407評論 0 2
  • 正文 前言說到java內(nèi)部類,想必大家首先會想到比較常用的“匿名內(nèi)部類”音念,但實際上沪饺,這只是內(nèi)部類的其中一種使用方式...
    java部落閱讀 477評論 0 6
  • 時間總是走的很快,春去秋來闷愤,又要到一年寒冬時節(jié)整葡。樹葉綠了又黃,轉(zhuǎn)眼即將化成來年的肥料讥脐,光禿禿的樹干上掘宪,偶爾有小鳥停...
    蘇穆涼閱讀 311評論 0 1
  • 一晃五六年坟漱,匆匆又夏天鼠次! 有的人,盡管很多年不聯(lián)系芋齿,但卻可以一直住在你心里很久很久很久腥寇,因為是在你最特別的時候陪伴...
    voler22閱讀 409評論 4 4