1. 程式人生 > >內部類引用外部類的區域性變數要用final修飾

內部類引用外部類的區域性變數要用final修飾

為什麼內部類引用外部類的區域性變數時,此變數要用final修飾
程式碼

   public void test() {

       final int i = 3;

       runOnUiThread(new Runnable() {
           @Override
           public void run() {
               mTvShow.setText(String.valueOf(i));
           }
       });
   }
  上面的程式碼是使用了匿名內部類的方式。Runnable是一個介面,此內部類實現了Runnable方法,重寫了接口裡面的run()方法,方法內引用了外部方法體內的區域性變數 i,這個時候i只能被定義為final變數,否則編譯會報錯。

  我們可以從JVM的角度去解釋這個現象,在編譯期的時候,所有的類都會被編譯成Class檔案。匿名內部類也會被編譯成Class檔案。但是上面的例子的內部類編譯會和我們所知道的普通類編譯方式會有些不同。 
  大多數的類在編譯的時候,需要知道每個方法需要為其所有的區域性變數分配多少記憶體。所以它會去檢查方法內定義的變數,從而確定此方法到真正執行的時候需要在棧中開闢多少記憶體。但這只是計算需要多少記憶體,真正分配記憶體是在執行期。 
  所以可以發現匿名內部類不同的是,雖然方法中的i沒有定義,但是在編譯期會給它分配額外的記憶體,並給它賦與外部的i同一個值。但是此變數已經不是外部的區域性變量了。在記憶體的角度上看,匿名內部類的i實際上不是外部的i,它們使用的記憶體空間都不同,只是它們的值相同

。 
  其實本質上來說,完全可以當作兩個不同的變數去使用,但是Java的設計人員可能想要保持一致性,因為Java的初學者在不瞭解其中真正的機制的時候,會以為他們就是同一個變數,所以乾脆就把變數強制定義為final,這樣變數就不能被重新賦值,營造一種他們是同一個變數的“假象”。

為什麼引用外部類的成員變數的時候,又不用final修飾呢?
  有一點要注意,當引用的不是區域性變數而是外部類的成員變數的時候,不一樣要用final修飾,因為它不需要像上面說的那樣需要在棧中重新開闢一個空間,而是內部類持有外部類的引用,可以直接引用外部類的成員變數。

與C/C++的不同
  在C++中,同樣的現象,引用外部類的區域性變數時,是不用加final了。其根本原因是出在,C++在編譯期的時候就進行動態連線了,而Java是在執行期的時候才進行動態連線。 
  說白了,就是C++的編譯時就已經分配好了空間,自然外部類和匿名內部類的i其實用的是同一塊記憶體區域,是真正意義上的同個變數,所以不需要加final。