天天看點

Java内部類

Java内部類真的很難了解,但有必要搞懂,因為内部類讓外部類更豐富多彩了,就好像一個人的心中還可以住着另外一個人。

01、前言

昨天晚上,我把車停好以後就回家了。回家後才發現手機落在車裡面了,但外面太冷,冷到骨頭都能感受到寒意——實在是不想傳回一趟去取了(小區的安保還不錯,不用擔心被砸車玻璃),于是打定主意過幾個小時的“世外桃源”生活——别人找不到我,我也找不到别人,這種與世隔絕的狀态非常适合讀書寫作。

把厚厚的《Java程式設計思想》擺在桌子上,正襟危坐,認認真真地讀起了第十章——内部類。盡管我已經非常耐心和用心了,但内部類的這一章非常的枯燥,并且難以了解,我整個人幾乎處于崩潰的邊緣。

很早之前,有想要轉行學習Java的朋友咨詢我,有哪方面的書可以推薦,我鄭重其事地介紹了《Java程式設計思想》,并且一再叮囑他這是一本Java入門級的經典書,必須耐着性子讀完它。現在想想,自己當時的推薦真是輕率!

我這樣說,并不是為了否認《Java程式設計思想》這本書的價值,因為站在書本的角度,它可能會感慨說:這王二的學習能力有問題啊,讀我竟然這麼困難!

不是有那樣一句話嘛:“如果你手裡有一把錘子,所有東西看上去都像釘子。”我認為“内部類”這一章很難懂,其根本的原因在于我對“内部類”沒有很好的了解。想要繼續紮實Java的基礎知識,唯一要做的就是——想盡一切辦法搞懂“内部類”,并梳理成文。

02、内部類的定義

顧名思義,内部類就是放在另外一個類的内部定義的類。非常重要的一點是,内部類能夠通路外部類的所有成員,包括private修飾的。

來看程式清單1-1:

public class Wanger {
    private int age;
    public Wanger(int age) {
        this.age = age;
    }
    
    class Thought {
        public void know() {
            System.out.println("沉默王二的年齡" + age);
        }
    }
    
    public Thought getThought() {
        return new Thought();
    }

    public static void main(String[] args) {
        Wanger wanger = new Wanger(29);
        Wanger.Thought thought = wanger.getThought();
        thought.know(); // 輸出:沉默王二的年齡29
        
        // 使用.new的形式建立内部類對象
        Wanger.Thought thought1 = wanger.new Thought();
        thought1.know();
    }
}

      

程式清單1-1要表達什麼意思呢?

答案是:我,沉默王二,已經29歲了,89年出生(有人說89年出生明明是30歲)。上了年紀了,總想裝點嫩,了解一下。我讀書不多,但特别愛思考,于是我就給自己建立了一個會思考的内部類Thought。

從程式清單1-1可以看得出,盡管Thought是内部類,但可以通路外部類Wanger的私有成員變量age。

如果想建立内部類的對象,需要先指明對象引用的類型,格式為 OuterClassName.InnerClassName,就像main()方法中的Wanger.Thought那樣。

緊接着,就要來建立内部類對象了,有兩種形式。第一種形式是先在外部類中定義一個方法Thought getThought(),傳回使用new關鍵字建立的内部類對象,然後使用外部類對象調用該方法wanger.getThought();第二種形式是直接通過外部類對象.new建立wanger.new Thought()。

03、匿名内部類

以我的程式設計經驗來看,匿名内部類使用最頻繁的場合就是在建立線程的時候。

來看程式清單2-1:

public class Demo {

    public void test(String title) {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                // title = "我不要吃雞";
                // 改變時會提示錯誤
                // 在封閉範圍中定義的局部變量必須是final的。
                System.out.println(title);
            }
        });
        thread.start();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Demo demo = new Demo();
            demo.test("我要吃雞" + i);
        }
    }
    
}

      

在程式清單2-1中,test()方法内部有一個線程對象thread,是通過new Thread()建立的。new Thread()可以接收一個實作了Runnable接口類型的對象,這個對象要怎麼建立呢?可以通過匿名内部類的形式來建立——new Runnable() {public void run(){......}}——這段簡短的代碼等同于:

// 實作Runnable接口
class MyRunnable implements Runnable {

    @Override
    public void run() {
        
    }
}

// 向上轉型
Runnable myRunnable = new MyRunnable();

      

匿名内部類的好處就在于不僅節省了定義實作類的過程,還能夠自動向上轉型。

在程式清單2-1中,test()方法還有一個參數title,JDK1.8之前,編譯器要求它必須是final類型的。但JDK1.8之後,如果我們在匿名内部類中需要通路局部變量,那麼這個局部變量不再需要用final關鍵字修飾了。

但如果想要在匿名内部類中改變局部變量的值,編譯器就會提醒你不能這樣做,它會提示:“在封閉範圍中定義的局部變量必須是final的。”

04、為什麼需要内部類

Java的内部類讓我很容易的想起來JavaScript的閉包,閉包就是定義在一個函數内部的函數——這聽起來和Java的内部類定義一樣一樣的。本質上,閉包是将函數内部與函數外部連接配接起來的橋梁。内部類一樣,它是将内部類與外部類連接配接起來的橋梁。

來看看什麼是閉包吧:

function wanger() {
    var age = 30;
    function know() {
        console.log(age);
    }
}

wanger();
// 控制台輸出30