Java匿名類遇上final

時間: 2018/10/19

Content

  1. final的普通語義

  2. final遇見內(nèi)部類

  3. 閉包

  4. 內(nèi)存泄漏

    ?

1. final的普通語義

關(guān)于Java中final關(guān)鍵字的常規(guī)語義就是表明其修飾的對象是不可變的曹锨, 被修飾的對象通常有. 值變量喷众,引用變量赃梧,類掂僵,函數(shù)。此處需要注意的是昆码,如果final修飾的是引用變量气忠,那么引用變量的值(地址)不可變,但是引用變量值對應(yīng)的對象實可以變的赋咽。

分別介紹一下修飾不同對象的情況:

  • 1). 值變量
  • 2). 引用變量
  • 3). 類
  • 4). 方法
// 1)
final int a = 1;
a = 1; // 這個語句會報錯旧噪,不允許修改

// 2)
final Map map = new HashMap();
map.put("key", "value"); // map值(地址)對應(yīng)對象(在堆)可以被修改,一般認(rèn)為對象被生成以后脓匿,其地址就是確定了
map = new HashMap(); // 會報錯, map值(地址)不允許修改

// 3)
final class Cls{
// .....
}
class SubCls extends Cls{ //編譯報錯淘钟,Cls不能為繼承
  // ....
}

// 4)
class Parent{
  public final void method(){
    // ...
  }
}   
class Child extends Parent{
  public void mehtod(){ // 編譯報錯,不允許覆蓋
    // ..
  }
}

2. final遇見內(nèi)部類

Java中要求如果方法中定義的中類如果引用方法中的局部變量陪毡,那要要求局部變量必須要用final修飾(JDK8中已經(jīng)不需要日月,但是本質(zhì)也是和final類似——只讀)袱瓮,實例代碼如下:

interface Inner{
  void method();
}

class Outer{
    public Inner createInner(){
        final int a = 12;
        final Map map = new HashMap();
        Inner inner = new Inner(){
            public void method(){
                int b = a + 1;
                System.out.println(" in Inner, b=" + b);
                map.put("innerKey", "innerValue");
            }
        };
        System.out.println("in Outer, createInner finish缤骨!");
        return inner;
    }

    public static void main(String []args){
        Inner inner = new Outer().createInner();
        inner.method();
    }
}

輸出如下

in Outer, createInner finish爱咬!
in Inner, b=13

Note: 上述代碼僅僅是展示使用,其中createInner()方法中的map變量是存在內(nèi)存泄漏的绊起,因為外界無法訪問他精拟,但是卻會被一致持有。關(guān)于內(nèi)存泄漏的問題虱歪,通過查看上述代碼便后的class文件的內(nèi)容即可發(fā)現(xiàn)蜂绎。

上述文件編譯后,生成了三個文件

  • Inner.class

  • Outer.class

  • Outer$1.class

    打開Outer$1.class可以看到如下內(nèi)容:

  class Outer$1 implements Inner {
      Outer$1(Outer this$0, int var2, Map var3) {
          this.this$0 = this$0;
          this.val$a = var2;
          this.val$map = var3;
      }
      
      public void method() {
          int b = this.val$a + 1;
          System.out.println(" in Inner, b=" + b);
          this.val$map.put("innerKey", "innerValue");
      }
  }

可以看到編譯后的內(nèi)容笋鄙,Inner匿名類擁有另一個帶有三個參數(shù)的構(gòu)造方法师枣,

  • Outer this$0: 也就是擁有了Outer(外部類)當(dāng)前對象的一個引用,所以我們Inner的子類中萧落,可以通過Outer.this訪問外部Outer類的當(dāng)前實例践美。
  • var2: 此處應(yīng)該為Outer createInner()方法中的局部變量a
  • var3: 此處應(yīng)該為Outer createInner()方法中的局部變量Map

通過上述編譯后的代碼,我們大概可以明白為什么匿名類可以訪問其外部數(shù)據(jù)的原因找岖,接下來我們可以討論一下為什么要對createInner()中的局部變量a, map用final進(jìn)行修飾陨倡。

網(wǎng)絡(luò)上有很多人說是生命周期的問題,但是我覺得不是這個原因许布,也覺得不存在生命周期的問題(歡迎討論)兴革。

為了簡化表述,以下將Inner匿名類里面的a表述為Inner().a蜜唾, 將createInner()方法中的a表示為 createInner.a.

通過編譯后的代碼可以看出來杂曲,Inner().acreateInner.a不是同一個對象(在內(nèi)存中不是同一個), 同樣的兩個map(值袁余,存在于堆)在內(nèi)存中也是不同的擎勘,但是兩個map的都指向了堆上的同一個HashMap對象。理論上我們是可以重新設(shè)置Inner().aInner().map的值的泌霍,但是java編譯器并不允許這樣做货抄, 具體原因我認(rèn)為可能是如下原因:

在匿名類內(nèi)部訪問外面的變量看起來是一個很正常的需求,而且直觀看起來應(yīng)該是同一個東西朱转。但是在方法調(diào)用結(jié)束以后局部變量會被銷毀(棧里面的內(nèi)容蟹地,也就是createInner.a, createInner.map。如果是同一個東西的話藤为,那么意味著jvm在方法調(diào)用結(jié)束以后還不能銷毀這些局部變量怪与,需要將這些局部變量的生命周期保持到和Inner一樣長,這樣讓jvm的實現(xiàn)起可能會更為復(fù)雜(提升這些變量的生命周期)缅疟。

所以分别,為了實現(xiàn)在Inner中可以訪問createInner()中的a, map遍愿,同時他們看起來和createInner()中的一樣(一致),并且避免JVM對對象生命周期的管理過于復(fù)雜耘斩,采用了一個中折中的辦法:

  1. 將被用到的變量作為Inner的構(gòu)造函數(shù)參數(shù)傳入并在Inner內(nèi)部設(shè)置對應(yīng)的實例(private)沼填。
  2. createInner().a, createInner().map設(shè)置為final,并且匿名類類部不可以修改對應(yīng)實例屬性的值括授,保證一致性坞笙。

通過上述的 1中,可以很自然實現(xiàn)在Inner中很自然的訪問createInner中局部變量的值荚虚;由于Inner中使用的變量實際上和外部函數(shù)中的局部變量是不一樣的薛夜,通過上述2可以保證他們一致(都不允許修改了,肯定一致)版述, 否則開發(fā)者在內(nèi)部修改值梯澜,但是卻不會影響到外面的局部變量,這會讓人困惑(天然看起來應(yīng)該是一個東西啊渴析,但是卻不能一起變化)晚伙。

3. 閉包

此處引出了Java對閉包的支持,其實Java目前是支持了閉包的檬某,匿名類就是一個典型的例子撬腾。將自由變量(createInner.a,createInner.map)封裝到Inner中,但是Java的閉包確實有條件的閉包恢恼,因為Java只實現(xiàn)了capture-by-value民傻, 只是把局部變量的值copy到了匿名類中, 沒有實現(xiàn)capture-by-reference场斑。如果是capture-by-reference的實現(xiàn)方式漓踢,可能需要將局部變量提升到對象中(也就是講局部變量的生命周期延長,變?yōu)楹?code>Inner類一樣長, 那么在createInner()執(zhí)行完畢以后漏隐,就不會銷毀 a, map了)喧半。

關(guān)于閉包的定義:Ruby之父松本行弘在《代碼的未來》一書中解釋的最好:閉包就是把函數(shù)以及變量包起來,使得變量的生存周期延長青责。

此處有一個系列的參考文章關(guān)于``Javascript```中閉包的內(nèi)容挺据,圖文并茂。深入理解javascript原型和閉包]

4. 內(nèi)存泄漏

Java中并沒有真正的實現(xiàn)延長生命周期脖隶, 但是間接實現(xiàn)了createInner.map的生命周期扁耐,因為Inner.map是一個對實際的HashMap()(位于堆中)對象的引用, 所以在createInner()方法中創(chuàng)建产阱,但是卻不會在該方法執(zhí)行以后被GC回收婉称, 該對象的生命周期和其創(chuàng)建Inner實例一樣長。在本例中的代碼的內(nèi)存泄漏就由此而生。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末王暗,一起剝皮案震驚了整個濱河市悔据,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俗壹,老刑警劉巖科汗,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異策肝,居然都是意外死亡肛捍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門之众,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人依许,你說我怎么就攤上這事棺禾。” “怎么了峭跳?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵膘婶,是天一觀的道長。 經(jīng)常有香客問我蛀醉,道長悬襟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任拯刁,我火速辦了婚禮脊岳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘垛玻。我一直安慰自己割捅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布帚桩。 她就那樣靜靜地躺著亿驾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪账嚎。 梳的紋絲不亂的頭發(fā)上莫瞬,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音郭蕉,去河邊找鬼疼邀。 笑死,一個胖子當(dāng)著我的面吹牛恳不,可吹牛的內(nèi)容都是我干的檩小。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼烟勋,長吁一口氣:“原來是場噩夢啊……” “哼规求!你這毒婦竟也來了筐付?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阻肿,失蹤者是張志新(化名)和其女友劉穎瓦戚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丛塌,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡较解,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赴邻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片印衔。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖姥敛,靈堂內(nèi)的尸體忽然破棺而出奸焙,到底是詐尸還是另有隱情,我是刑警寧澤彤敛,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布与帆,位于F島的核電站,受9級特大地震影響墨榄,放射性物質(zhì)發(fā)生泄漏玄糟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一袄秩、第九天 我趴在偏房一處隱蔽的房頂上張望阵翎。 院中可真熱鬧,春花似錦播揪、人聲如沸贮喧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箱沦。三九已至,卻和暖如春雇庙,著一層夾襖步出監(jiān)牢的瞬間谓形,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工疆前, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寒跳,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓竹椒,卻偏偏與公主長得像童太,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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