時間: 2018/10/19
Content
final的普通語義
final遇見內(nèi)部類
閉包
-
內(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().a
和createInner.a
不是同一個對象(在內(nèi)存中不是同一個), 同樣的兩個map(值袁余,存在于堆)在內(nèi)存中也是不同的擎勘,但是兩個map的都指向了堆上的同一個HashMap
對象。理論上我們是可以重新設(shè)置Inner().a
和Inner().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ù)雜耘斩,采用了一個中折中的辦法:
- 將被用到的變量作為
Inner
的構(gòu)造函數(shù)參數(shù)傳入并在Inner
內(nèi)部設(shè)置對應(yīng)的實例(private)沼填。- 將
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)存泄漏就由此而生。