1. 程式人生 > >匿名內部類為什麼是 Final 的呢?

匿名內部類為什麼是 Final 的呢?

寫在前面

匿名內部類來自外部閉包環境的自由變數必須是final的,除非自由變數來自類的成員變數。

什麼是自由變數?
一個函式的“自由變數”就是既不是函式引數也不是函式內部區域性變數的變數。

什麼是外部環境
外部環境如果持有內部函式所使用的自由變數,就會對內部函式形成“閉包”。

    -

一個簡單的列子

public class AnonymousDemo1
{
    public static void main(String args[])
    {
        new AnonymousDemo1().play();
    }

    private
void play() { Dog dog = new Dog(); Runnable runnable = new Runnable() { public void run() { while(dog.getAge()<100) { // 過生日,年齡加一 dog.happyBirthday(); // 列印年齡
System.out.println(dog.getAge()); } } }; new Thread(runnable).start(); // do other thing below when dog's age is increasing // .... } }
public class Dog
{
    private int age;

    public int getAge()
    {
        return
age; } public void setAge(int age) { this.age = age; } public void happyBirthday() { this.age++; } }

編譯上面的程式碼,編譯出錯

Variable ‘dog’ is accessed from within inner class,need to be declared final

意思說,需要將 dog 變數定義為 final 型別,可是為什麼要定義為 final 型別,final 型別的 變數有什麼優勢呢?

  final Dog dog = new Dog();

一個被final修飾的變數:
如果這個變數是基本資料型別,那麼它的值不能改變;

暫且先放一下上面的疑問,看看匿名內部類到底長什麼樣子

反編譯匿名內部類

反編譯之後的內名內部類程式碼如下:

class AnonymousDemo1$1 implements Runnable {

    final Dog val$dog;
    final AnonymousDemo1 this$0;

    AnonymousDemo1$1(final_anonymousdemo1,Dog)
    {
        this$0 = final_anonymousdemo1;
        val$dog = Dog.this;
        super();
    }

    public void run()
    {
        for(; val$dog.getAge() < 100; System.out.println(val$dog.getAge()))
            val$dog.happyBirthday();

    } 
}

1 匿名內部類的建構函式,有兩個引數:一個是外部類物件的引用,一個是區域性變數 Dog 物件的引用,因為下面的 run 方法會使用到該物件。
2 那麼 外部類 AnonymousDemo1 可以等同於如下的程式碼

public class AnonymousDemo1
{
    public static void main(String args[])
    {
        new AnonymousDemo1().play();
    }

    private void play()
    {
        Dog dog = new Dog();
        AnonymousDemo1$1 runnable = new AnonymousDemo1$1(this,dog)
        new Thread(runnable).start();
    }
}

還是沒有回答主要問題

到這裡我們已經看清匿名內部類的全貌了,其實Java就是把外部類的一個變數拷貝給了內部類裡面的另一個變數。這個例子中,無論是內部類的val$dog變數,還是外部類的dog變數,他們都只是一個儲存著物件例項地址的變數而已,而由於做了拷貝,這兩個變數指向的其實是同一只狗(物件)。
這裡寫圖片描述

因此,這個例子中,假如我們不加上final,那麼我可以在程式碼後面加上這麼一句dog = new Dog(); 就像下面這樣:

// ...
new Thread(runnable).start();
// do other thing below when dog's age is increasing
dog = new Dog();

這樣,外面的dog變數就指向另一隻狗了,而內部類裡的val$dog,還是指向原先那一隻,就像這樣:
這裡寫圖片描述

**這樣做導致的結果就是內部類裡的變數和外部環境的變數不同步,指向了不同的物件。
因此,編譯器才會要求我們給dog變數加上final,防止這種不同步情況的發生。**

那為什麼要拷貝呢

1 拷貝了什麼東西?
將 dog 的引用地址 拷貝到了 val$dog

2 為什麼要拷貝?
這時候就得考慮一下Java虛擬機器的執行時資料區域了,dog變數是位於方法內部的,因此dog是在虛擬機器棧上,也就意味著這個變數無法進行共享,匿名內部類也就無法直接訪問,因此只能通過值傳遞的方式,傳遞到匿名內部類中。

哪種情況不需要拷貝呢?

拷貝的原因,是因為 內部類中 無法 直接訪問外部方法中定義 的 dog 變數,如果可以直接訪問,那麼就沒必要拷貝 應用了

我們修改一下 AnonymousDemo1 程式碼,將 dog 變數提升為 成員屬性

private Dog dog = new Dog();

接下來反編譯一下 AnonymousDemo1$1 類


class AnonymousDemo1$1 implements Runnable
{

    final AnonymousDemo1 this$0;

    AnonymousDemo1$1()
    {
        this$0 = AnonymousDemo1.this;
        super();
    }

    public void run()
    {
        for(; AnonymousDemo1.access$000(AnonymousDemo1.this).getAge() < 100; System.out.println(AnonymousDemo1.access$000(AnonymousDemo1.this).getAge()))
            AnonymousDemo1.access$000(AnonymousDemo1.this).happyBirthday();

    }

}

1 現在 匿名內部類 不在需要 dog 變數的拷貝了,只需要引用 上層的 外部類物件就好了, 因為可以通過 外部類物件直接 獲取到 dog 物件了,因而編譯器也就不要求加上final了。

寫在最後

匿名內部類來自外部閉包環境的自由變數必須是final的”:

首先,自由變數是什麼?
一個函式的“自由變數”就是既不是函式引數也不是函式內部區域性變數的變數,這種變數一般處於函式執行時的上下文,就像demo中的dog,有可能第一次執行時,這個dog指向的是age是10的狗,但是到了第二次執行時,就是age是11的狗了。

然後,外部閉包環境是什麼?
外部環境如果持有內部函式所使用的自由變數,就會對內部函式形成“閉包”,demo1中,外部play方法中,持有了內部類中的dog變數,因此形成了閉包。
當然,demo2中,也可以理解為是一種閉包,如果這樣理解,那麼這句經典的話就應該改為這樣更為準確:
匿名內部類來自外部閉包環境的自由變數必須是final的,除非自由變數來自類的成員變數,僅針對 Java 語言 而言。

相關推薦

匿名部類為什麼是 Final

寫在前面 匿名內部類來自外部閉包環境的自由變數必須是final的,除非自由變數來自類的成員變數。 什麼是自由變數? 一個函式的“自由變數”就是既不是函式引數也不是函式內部區域性變數的變數。 什麼是外部環境 外部環境

匿名部類可以訪問的變數---靜態成員變數和final修飾的區域性變數

  在學習多執行緒的時候用到了匿名內部類,匿名內部類可以訪問static靜態成員變數或者final修飾的區域性變數。   匿名內部類在編譯之後會生成class檔案,比如Test內的第一個匿名內部類編譯之後就是Test$1.class;   匿名內部類中訪問的final修飾的區域性變數在生成Test$1.c

關於為什麼jdk 8以前匿名部類引數必須為final型別的問題

我們先來看一段程式碼   public class Hello {     public static void main(String[] args) {         String str=

Java匿名部類中使用外部類方法的形參或區域性變數必須宣告為final

 對於這個問題,首先我們應該明確的一點是對於匿名內部類,它可能引用三種外部變數:外部類的成員變數外部方法或作用域內的區域性變數外部方法的引數而第一種變數是不需要宣告為final的,但後兩種是需要宣告為final的。那這是為什麼呢?不急,我們首先來看第一個知識點。知識點一,匿名內部類同所有類一

為什麼匿名部類和區域性部類只能訪問被final修飾的區域性變數?

匿名內部類是形如下面程式碼中的類(Inner):   匿名內部類必須繼承一個父類或實現一個介面,但最多隻能實現一個介面。 上圖中,匿名內部類的父類就是Outer類。 而區域性內部類是定義在方法中、程式碼塊中、構造器中的類。 形如下圖中的類:    

匿名部類訪問方法成員變數需要加final的原因及證明

在java程式設計中,沒用的類定義太多對系統來說也是一個負擔,這時候我們可以通過定義匿名內部類來簡化程式設計,但匿名內部類訪問外部方法的成員變數時都要求外部成員變數新增final修飾符,final修飾

Java部類詳解 及 區域性部類匿名部類只能訪問區域性final變數的原因

說起內部類這個詞,想必很多人都不陌生,但是又會覺得不熟悉。原因是平時編寫程式碼時可能用到的場景不多,用得最多的是在有事件監聽的情況下,並且即使用到也很少去總結內部類的用法。今天我們就來一探究竟。下面是本文的目錄大綱:   一.內部類基礎   二.深入理解內部類   三.內部類的使用場景和好處   

-1-2 java 面向物件基本概念 封裝繼承多型 變數 this super static 靜態變數 匿名物件 值傳遞 初始化過程 程式碼塊 final關鍵字 抽象類 介面 區別 多型 包 訪問許可權 內部類 匿名內部類 == 與 equal

java是純粹的面向物件的語言 也就是萬事萬物皆是物件 程式是物件的集合,他們通過傳送訊息來相互通訊 每個物件都有自己的由其他的物件所構建的儲存,也就是物件可以包含物件 每個物件都有它的型別  也就是類 某一特定型別的所有物件都可以接收相同的訊息,因為同一類事物有共同的特性 面向物件開發 •

為什麼匿名部類用到的變數必須定為final

如果說匿名內部類無法被繼承,那麼也只能說匿名內部類是final的。如果一個類是final的,那麼所有屬於這個類的方法是final的,但它的成員變數並不是。而且被final的變數還是外部類的,外部類沒有必要不讓自己修改自己的變數的值。上述情況其實只發生在內部類引用的變數不是成員

匿名部類和區域性部類中只能訪問final變數的原因

在一個方法中,如果建立一個匿名內部類,並且在內部類中藥訪問方法的引數,我們必須將該引數用final進行修飾才能訪問,這個的原因主要是由於方法和內部類的生命週期不相同導致的。 當我們執行一個帶參的方法時,在該方法的呼叫棧中將生成一個區域性變數(即該引數),如果在該方法中建立了

為什麼匿名部類引數必須為final型別

1)  從程式設計語言的理論上:區域性內部類(即:定義在方法中的內部類),由於本身就是在方法內部(可出現在形式引數定義處或者方法體處),因而訪問方法中的區域性變數(形式引數或區域性變數)是天經地義的.是很自然的 2)  為什麼JAVA中要加上一條限制:只能訪問final型的區域性變數? 3)  JAV

java提高篇(十)-----詳解匿名部類 ,形參為什麼要用final

在java提高篇-----詳解內部類中對匿名內部類做了一個簡單的介紹,但是內部類還存在很多其他細節問題,所以就衍生出這篇部落格。在這篇部落格中你可以瞭解到匿名內部類的使用、匿名內部類要注意的事項、如何初始化匿名內部類、匿名內部類使用的形參為何要為final。 一、使用

詳解匿名部類 ,形參為什麽要用final

創建方式 構造器 引用 roi nbsp this 外部 out 拆分 一、使用匿名內部類內部類 匿名內部類由於沒有名字,所以它的創建方式有點兒奇怪。創建格式如下: new 父類構造器(參數列表)|實現接口() { //匿名

java匿名部類 (轉載)

demo .cn 抽象方法 tab trac str adding strac oid 匿名內部類也就是沒有名字的內部類 正因為沒有名字,所以匿名內部類只能使用一次,它通常用來簡化代碼編寫 但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口 實例1:不使用匿

Java部類匿名部類

urn nat 看到了 math 通過 rri 內部 test mat ??我們都知道Java中可以使用內部類,將一個類的定義放在另一個類的定義的內部,這就是內部類,但是匿名內部類往往使我們摸不著頭腦,因為它並沒有特定的名稱,那麽該如何使用它呢? 定義一個匿名內部類 pu

java匿名部類的使用註意事項

trac 操作 num abs nal 部分 ets void name 1、首先匿名內部類要繼承自抽象基類或者實現基類接口 like this abstract class Seed{ int cnt; public Seed(int x){ cnt

為什麽說Java匿名部類是殘缺的閉包

pan 年齡 pos 發生 clas 接下來 對象的引用 編譯器 xpl 前言 我們先來看一道很簡單的小題: public class AnonymousDemo1 { public static void main(String args[]) {

java匿名部類

實現 lar 編寫 void pri com href show api show the code : package com.test.jwen.httpApiAuto; public class AInter { publi

部類匿名部類

良好的 運行 實例 基本實現 產生 final 嚴重 代碼 組成 內部類不是很好理解,但說白了其實也就是一個類中還包含著另外一個類 如同一個人是由大腦、肢體、器官等身體結果組成,而內部類相當於其中的某個器官之一,例如心臟:它也有自己的屬性和行為(血液、跳動) 顯然,此

JavaSE8基礎 多線程 匿名部類既重寫Thread中run,又實現Runnable中run

run compile 去掉 思考 release mpi window generate fix 禮悟: 好好學習多思考,尊師重道存感恩。葉見尋根三返一,活水清源藏於零。 虛懷若谷良心主,皓月當空自在王。願給最苦行無悔,誠勸且行且珍