Java內部類訪問局部變量時的final問題

JAVA用了也快三年了,內部類訪問局部變量的情況也沒少遇到。也一直知道要給變量加個final修飾符,不然通過不了編譯。但一直也沒深究過爲什麼要加。昨天好奇的上網查了下,並翻閱了下相關的書籍(Core Java 8th),終於算是搞明白了,在這裏簡單說明下。


說先我們來看一段示例代碼:

01 public void start(int interval,final boolean beep) {
02
03     // Inner Class
04     class TimePrinter implements ActionListener {
05
06         @Override
07         public void actionPerformed(ActionEvent event) {
08             Date now = new Date();
09             System.out.println(“At the tone, the time is “ + now);
10             if (beep) {
11                 Toolkit.getDefaultToolkit().beep();
12             }
13         }
14     }
15     //
16     ActionListener listener = new TimePrinter();
17     Timer t = new Timer(interval, listener);
18     t.start();
19 }

我們在start函數中定義了一個內部類TimePrinter,其中訪問了函數的局部變量beep

爲了說明內部類訪問局部變量爲什麼要加final關鍵字,我們先來看一下JAVA對內部類的實現。

假設上述代碼中start函數所在的類的名稱爲TalkingClock。則編譯上述代碼的時候,JAVA編譯器會把TimePrinter內部類編譯爲一個獨立的class文件。其形式如下(類名爲:外部類$內部類):

01 class TalkingClock$1TimePrinter {
02     // 添加的構造函數,參數爲外部類對象的引用和該內部類訪問的局部變量的引用(這裏爲boolean類型)
03     TalkingClock$1TimePrinter(TalkingClock, boolean);
04     // 內部類原有的函數
05     public void actionPerformed(java.awt.event.ActionEvent);
06     // 局部變量的引用
07     final boolean val$beep;
08     // 外部類對象的引用
09     final TalkingClock this$0;
10 }

通過上述類的定義,我們可以看出內部類在構造的時候,會被編譯器自動傳入外部類對象的一個引用,同時也會傳入內部類訪問的局部變量的引用,這也就解釋了內部類對象爲什麼可以訪問外部類的成員變量和函數還有局部變量了。但是由於這些工作是在編譯時進行的,JAVA虛擬機並沒有什麼所謂的內部類的概念,在JAVA虛擬機看來,該內部類和外部類是兩個獨立的class文件。我們知道,一個類的私有函數和成員變量是不能被其他類訪問的。那麼內部類又是如何訪問外部類的私有成員變量和函數呢?

我們假設外部類中有一個私有的int型的變量counter。我們想在內部類中訪問它。其實在編譯的時候,爲了內部類可以訪問外部類的私有變量,JAVA編譯器還偷偷做了一些額外的工作。編譯器除了上文提到的會生成一個內部類類,同時還會修改我們的外部類代碼。其修改如下:

1 class TalkingClock {
2     // 編譯器自動添加的函數,用來訪問私有成員變量counter
3     static int access$0(TalkingClock);
4     // 原有的函數
5     public void start();
6     // 私有成員變量
7     private int counter
8 }


可以看出,爲了訪問counter私有成員變量,編譯器偷偷的爲我們添加了一個access$0的靜態函數,它接收一個TalkingClock對象的引用,並返回該對象內的coutner的值。並且編譯器會把我們在內部類中用到counter的地方都替換爲TalkingClock.access$0(this$0)

好了,現在我們已經瞭解了內部類的實現機制,那我們最後來看一下訪問局部變量爲什麼要求局部變量添加final關鍵字。

還是以我們最開始的那個start函數爲例。我們來看一下該函數的可能的執行過程:

如果beep變量不被標註爲final,那麼就意味着我們可以隨時修改beep的值。假設我們在創建了TimePrinter對象後修改了beep的值,那麼這時我們的內部類所看到的beep的值還是之前通過構造函數傳遞進去的老值,這樣就導致內部類和外部函數對beep值“認識”的不一致。所以final關鍵字的目的就是爲了保證內部類和外部函數對變量“認識”的一致性。

結束語:這個內部類final的問題從最開始學JAVA時就遇到了,期間也有想過爲什麼要加,但最終都沒有深究,就理所當然的認爲是規定了。其實這是一個很不好的習慣。學習東西就要“知其然,知其所以然”,一知半解最是害人。希望大家都可以引以爲鑑。

發佈了41 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章