目錄:
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)部類
略。