Java語法糖系列五:內(nèi)部類和閉包

目錄:
Java語法糖系列一:可變長度參數(shù)和foreach循環(huán)
http://www.reibang.com/p/628568f94ef8

Java語法糖系列二:自動裝箱/拆箱和條件編譯
http://www.reibang.com/p/946b3c4a5db6

Java語法糖系列三:泛型與類型擦除
http://www.reibang.com/p/4de08deb6ba4

Java語法糖系列四:枚舉類型
http://www.reibang.com/p/ae09363fe734

Java語法糖系列五:內(nèi)部類和閉包
http://www.reibang.com/p/f55b11a4cec2


內(nèi)部類

Java的內(nèi)部類也是一個語法糖,它僅僅是一個編譯時的概念刮刑,outer.java里面定義了一個內(nèi)部類inner哪工,一旦編譯成功吨述,就會生成兩個完全不同的.class文件了,分別是outer.class和outer$inner.class巩搏。所以內(nèi)部類的名字完全可以和它的外部類名字相同。

內(nèi)部類分為四種:成員內(nèi)部類、局部內(nèi)部類孽江、匿名內(nèi)部類、靜態(tài)內(nèi)部類番电。但本篇不是談?wù)撍姆N內(nèi)部類的用法的岗屏,只講內(nèi)部類一些值得注意的地方。

為什么要使用內(nèi)部類

在《Think in java》中有這樣一句話:使用內(nèi)部類最吸引人的原因是:每個內(nèi)部類都能獨立地繼承一個(接口的)實現(xiàn)漱办,所以無論外圍類是否已經(jīng)繼承了某個(接口的)實現(xiàn)这刷,對于內(nèi)部類都沒有影響。

因為Java不支持多繼承娩井,支持實現(xiàn)多個接口暇屋。但有時候會存在一些使用接口很難解決的問題,這個時候我們可以利用內(nèi)部類提供的洞辣、可以繼承多個具體的或者抽象的類的能力來解決這些程序設(shè)計問題率碾。可以這樣說屋彪,接口只是解決了部分問題所宰,而內(nèi)部類使得多重繼承的解決方案變得更加完整。

成員內(nèi)部類

成員內(nèi)部類也是最普通的內(nèi)部類畜挥,它是外圍類的一個成員仔粥,所以他是可以無限制的訪問外圍類的所有 成員屬性和方法,盡管是private的蟹但,但是外圍類要訪問內(nèi)部類的成員屬性和方法則需要通過內(nèi)部類實例來訪問躯泰。在成員內(nèi)部類中要注意兩點

  • 成員內(nèi)部類中不能存在任何static的變量和方法;

  • 成員內(nèi)部類是依附于外圍類的华糖,所以只有先創(chuàng)建了外圍類才能夠創(chuàng)建內(nèi)部類麦向。例子略。

局部內(nèi)部類和匿名內(nèi)部類

局部內(nèi)部類是嵌套在方法和作用域內(nèi)的客叉,對于這個類的使用主要是應(yīng)用與解決比較復(fù)雜的問題诵竭,想創(chuàng)建一個類來輔助我們的解決方案,到那時又不希望這個類是公共可用的兼搏,所以就產(chǎn)生了局部內(nèi)部類卵慰,局部內(nèi)部類和成員內(nèi)部類一樣被編譯,只是它的作用域發(fā)生了改變佛呻,它只能在該方法和屬性中被使用裳朋,出了該方法和屬性就會失效。

而匿名內(nèi)部類也可以說是局部內(nèi)部類的一種吓著,有時候一個類只使用一次鲤嫡,就可以用匿名內(nèi)部類送挑,告訴GC只用一次就可以回收了,同時也可以簡化代碼和方便地定義回調(diào)

需要注意的是局部內(nèi)部類和匿名內(nèi)部類引用外部變量時暖眼,外部的變量需要是final 的:

abstract class InnerClass {
public abstract void print();
}
public class Outer {
public  void test1(final String s1) {// 參數(shù)必須是final
    //成員內(nèi)部類
    InnerClass c = new InnerClass() {
        public void print() {
            System.out.println(s1);
        }
    };
    c.print();
}

public  void test2(final String s2) {// 參數(shù)必須是final
    //匿名內(nèi)部類
     new Outer() { //名字可以跟外部類一樣
        public void print() {
            System.out.println(s2);
        }
    }.print();
}    
public static void main(String[] args) {
    Outer o=new Outer();
    o.test1("inner1");
    o.test2("inner2");

}
}

為什么匿名內(nèi)部類和局部內(nèi)部類引用外部的變量必要是final的呢惕耕?
直接看編譯出來的源碼吧

InnerClass:

    abstract class InnerClass
    {
      public abstract void print();
    }

Outer.class:

`import java.io.PrintStream;

public class Outer
{
  public void test1(String paramString)
  {
Outer.1 local1 = new InnerClass(this, paramString) {
  public void print() {
    System.out.println(this.val$s1);//引用了s1變量
  }

};
local1.print(); }

  public void test2(String paramString) {
new Outer(this, paramString) {//名字可以一樣
  public void print() {
    System.out.println(this.val$s2);
  }
}
.print();
  }

 public static void main(String[] paramArrayOfString)
  {
Outer localOuter = new Outer();
localOuter.test1("inner1");
localOuter.test2("inner2");
  }
}

局部內(nèi)部類Outer$1.class:

import java.io.PrintStream;
class 1 extends InnerClass
{
  public void print()
  {
    System.out.println(this.val$s1);//引用了s1變量
  }
}

匿名內(nèi)部類Outer$2.class:

import java.io.PrintStream;
class 2 extends Outer
{
  public void print()
  {
    System.out.println(this.val$s2);//引用了s2變量
  }
}

看源碼兩個內(nèi)部類各編譯出了一個獨立的class文件,也就是說Outer$1和Outer$2的生命周期是對象級別的罢荡,而變量s1赡突、s2是方法級別的对扶,方法運行完了變量就銷毀了区赵,而局部內(nèi)部類對象Outer$1和Outer$2還可能一直存在(只能沒有人再引用該對象時,它才會被GC回收),它不會隨著方法運行結(jié)束就馬上死亡。這時可能會出現(xiàn)了一種"荒唐"的結(jié)果:局部內(nèi)部類對象inner_object要訪問一個已不存在的局部變量s1浪南、s2!

也有人說:當方法調(diào)用完了笼才,內(nèi)部類也不可能再被訪問到了,照理內(nèi)部類對象也應(yīng)該成為了垃圾络凿。
別忘了Java還有反射骡送,而且在多線程的情況下完全有可能主線程的方法運行結(jié)束,而內(nèi)部類還在運行,例如:

public void execute() {
  final int s = 10;
       class InnerClass {
          public void execute() {
              new Thread() {
              @Override
               public void run() {
                 try {
                        Thread.currentThread().sleep(2000);
                        System.out.println(s);
                 } catch (final InterruptedException e) {
                       e.printStackTrace();
                  }
               }
             }.start();
           }
         }
        new InnerClass().execute();
        System.out.println("主方法已經(jīng)over");
    } 

為什么把變量定義為final就能避免上述問題絮记?
看stackoverflow上的一個討論
http://stackoverflow.com/questions/3910324/why-java-inner-classes-require-final-outer-instance-variables

stackoverflow里最高票的答案說到摔踱,當主方法結(jié)束時,局部變量會被cleaned up 而內(nèi)部類可能還在運行怨愤。當局部變量聲明為final時派敷,當使用已被cleaned up的局部變量時會把局部變量替換成常量:

The compiler can then just replace the use of lastPrice and price in the anonymous class with the values of the constants (at compile time, ofcourse)

也就是說當變量是final時,編譯器會將final局部變量"復(fù)制"一份,復(fù)制品直接作為局部內(nèi)部中的數(shù)據(jù)成員.這樣,當局部內(nèi)部類訪問局部變量時,其實真正訪問的是這個局部變量的"復(fù)制品"撰洗。因此:當運行棧中的真正的局部變量死亡時,局部內(nèi)部類對象仍可以訪問局部變量(其實訪問的是"復(fù)制品")篮愉。而且,由于被final修飾的變量賦值后不能再修改差导,所以就保證了復(fù)制品與原始變量的一致试躏。給人的感覺:好像是局部變量的"生命期"延長了。
這就是java的閉包设褐。

最后貼兩段關(guān)于閉包的筆記颠蕴,來源于網(wǎng)絡(luò):

閉包是個什么東西呢?
Ruby之父松本行弘在《代碼的未來》一書中解釋的最好:閉包就是把函數(shù)以及變量包起來助析,使得變量的生存周期延長裁替。閉包跟面向?qū)ο笫且豢脴渖系膬蓷l枝,實現(xiàn)的功能是等價的貌笨。

Java中閉包帶來的問題
  在Java的經(jīng)典著作《Effective Java》弱判、《Java Concurrency in Practice》里,都提到:匿名函數(shù)里的變量引用锥惋,也叫做變量引用泄露昌腰,會導致線程安全問題开伏,因此在Java8之前,如果在匿名類內(nèi)部引用函數(shù)局部變量遭商,必須將其聲明為final固灵,即不可變對象。(Python和Javascript從一開始就是為單線程而生的語言劫流,一般也不會考慮這樣的問題巫玻,所以它的外部變量是可以任意修改的)。
  而java8的lambda 表達式之所以不用寫final祠汇,是因為Java8這里加了一個語法糖:在lambda表達式以及匿名類內(nèi)部仍秤,如果引用某局部變量,則直接將其視為final可很。本質(zhì)并沒有改變诗力。

靜態(tài)內(nèi)部類

略。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末我抠,一起剝皮案震驚了整個濱河市苇本,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菜拓,老刑警劉巖瓣窄,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纳鼎,居然都是意外死亡俺夕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門喷橙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啥么,“玉大人,你說我怎么就攤上這事贰逾⌒伲” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵疙剑,是天一觀的道長氯迂。 經(jīng)常有香客問我,道長言缤,這世上最難降的妖魔是什么嚼蚀? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮管挟,結(jié)果婚禮上轿曙,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好导帝,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布守谓。 她就那樣靜靜地躺著,像睡著了一般您单。 火紅的嫁衣襯著肌膚如雪斋荞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天虐秦,我揣著相機與錄音平酿,去河邊找鬼。 笑死悦陋,一個胖子當著我的面吹牛蜈彼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叨恨,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼柳刮,長吁一口氣:“原來是場噩夢啊……” “哼挖垛!你這毒婦竟也來了痒钝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤痢毒,失蹤者是張志新(化名)和其女友劉穎送矩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哪替,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡栋荸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凭舶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晌块。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖帅霜,靈堂內(nèi)的尸體忽然破棺而出匆背,到底是詐尸還是另有隱情,我是刑警寧澤身冀,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布钝尸,位于F島的核電站,受9級特大地震影響搂根,放射性物質(zhì)發(fā)生泄漏珍促。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一剩愧、第九天 我趴在偏房一處隱蔽的房頂上張望猪叙。 院中可真熱鬧,春花似錦、人聲如沸穴翩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽藏否。三九已至瓶殃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間副签,已是汗流浹背遥椿。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淆储,地道東北人冠场。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像本砰,于是被迫代替她去往敵國和親碴裙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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

  • Java 內(nèi)部類 分四種:成員內(nèi)部類点额、局部內(nèi)部類舔株、靜態(tài)內(nèi)部類和匿名內(nèi)部類。 1还棱、成員內(nèi)部類: 即作為外部類的一個成...
    ikaroskun閱讀 1,223評論 0 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法载慈,類相關(guān)的語法,內(nèi)部類的語法珍手,繼承相關(guān)的語法办铡,異常的語法,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • 一:java概述:1琳要,JDK:Java Development Kit寡具,java的開發(fā)和運行環(huán)境,java的開發(fā)工...
    ZaneInTheSun閱讀 2,635評論 0 11
  • 故事簡介: 游手好閑的主角蘭伯特意外得知黑龍魔物來攻擊人類首都稚补,于是趕回首都求救童叠,在路上不幸被包圍,但是戴著主角光...
    斐一所思閱讀 776評論 0 2
  • 一城煙雨孔厉,幾縷閑愁 壯闊是秦漢的狼煙 蒼涼是明清的夢 像月光屬于詩 叆叇在疊嶂 姜女哭碎 千年后 風雨 舊
    上好的雞湯閱讀 621評論 2 6