JDK8之前,匿名內(nèi)部類訪問(wèn)的局部變量為什么必須要用final修飾
更多博文請(qǐng)關(guān)注:https://blog.bigcoder.cn
前不久在學(xué)習(xí)中意外發(fā)現(xiàn)了自己原來(lái)忽略的一個(gè)小知識(shí)點(diǎn),挺有意思的,現(xiàn)在我來(lái)給大家分享一下!
我們先來(lái)看一段代碼
public class Hello {
public static void main(String[] args) {
String str="haha";
new Thread() {
@Override
public void run() {
System.out.println(str);
}
}.start();
}
}
現(xiàn)在我問(wèn)問(wèn)大家,這個(gè)打印的程序的結(jié)果是什么?
可能大部分人毫不猶豫的會(huì)說(shuō):打印“haha”。其實(shí)這個(gè)程序根本就編譯不通過(guò)(有點(diǎn)答非所問(wèn)的感覺(jué),哈哈)。
因?yàn)?strong>在JDK8之前,如果我們?cè)谀涿麅?nèi)部類中需要訪問(wèn)局部變量,那么這個(gè)局部變量必須用final修飾符修飾。這里所說(shuō)的匿名內(nèi)部類指的是在外部類的成員方法中定義的內(nèi)部類。既然是在方法中創(chuàng)建的內(nèi)部類,必然會(huì)在某些業(yè)務(wù)邏輯中出現(xiàn)訪問(wèn)這個(gè)方法的局部變量的需求。那么我們下面就會(huì)研究這種情況。
為什么java語(yǔ)法要求我們需要用final修飾呢?想了想沒(méi)有什么答案,那我們就通過(guò)jd-gui反編譯工具一探究竟,我們對(duì)匿名內(nèi)部類的字節(jié)碼文件進(jìn)行反編譯得到以下內(nèi)容。
我們可以看到匿名內(nèi)部類的構(gòu)造器中傳入了一個(gè)參數(shù),我們可以推理出這個(gè)參數(shù)就是底層傳入的str的值,但因?yàn)榉淳幾g工具的某種疏忽將構(gòu)造器的方法體寫(xiě)成了空,事實(shí)上真正的反編譯代碼應(yīng)該是下面:
public class Hello$1 extends Thread {
private String val$str;
Hello$1(String paramString) {
this.val$str = paramString;
}
public void run() {
System.out.println(this.val$str);
}
}
也就是說(shuō)匿名內(nèi)部類之所以可以訪問(wèn)局部變量,是因?yàn)樵诘讓訉⑦@個(gè)局部變量的值傳入到了匿名內(nèi)部類中,并且以匿名內(nèi)部類的成員變量的形式存在,這個(gè)值的傳遞過(guò)程是通過(guò)匿名內(nèi)部類的構(gòu)造器完成的。
那么問(wèn)題又來(lái)了,為什么需要用final修飾局部變量呢?
按照習(xí)慣,我依舊先給出問(wèn)題的答案:用final修飾實(shí)際上就是為了保護(hù)數(shù)據(jù)的一致性。
這里所說(shuō)的數(shù)據(jù)一致性,對(duì)引用變量來(lái)說(shuō)是引用地址的一致性,對(duì)基本類型來(lái)說(shuō)就是值的一致性。
這里我插一點(diǎn),final修飾符對(duì)變量來(lái)說(shuō),深層次的理解就是保障變量值的一致性。為什么這么說(shuō)呢?因?yàn)橐妙愋妥兞科浔举|(zhì)是存入的是一個(gè)引用地址,說(shuō)白了還是一個(gè)值(可以理解為內(nèi)存中的地址值)。用final修飾后,這個(gè)這個(gè)引用變量的地址值不能改變,所以這個(gè)引用變量就無(wú)法再指向其它對(duì)象了。
回到正題,為什么需要用final保護(hù)數(shù)據(jù)的一致性呢?
因?yàn)閷?shù)據(jù)拷貝完成后,如果不用final修飾,則原先的局部變量可以發(fā)生變化。這里到了問(wèn)題的核心了,如果局部變量發(fā)生變化后,匿名內(nèi)部類是不知道的(因?yàn)樗皇强截惲司植蛔兞康闹担⒉皇侵苯邮褂玫木植孔兞浚_@里舉個(gè)栗子:原先局部變量指向的是對(duì)象A,在創(chuàng)建匿名內(nèi)部類后,匿名內(nèi)部類中的成員變量也指向A對(duì)象。但過(guò)了一段時(shí)間局部變量的值指向另外一個(gè)B對(duì)象,但此時(shí)匿名內(nèi)部類中還是指向原先的A對(duì)象。那么程序再接著運(yùn)行下去,可能就會(huì)導(dǎo)致程序運(yùn)行的結(jié)果與預(yù)期不同。
介紹到這里,關(guān)于為什么匿名內(nèi)部類訪問(wèn)局部變量需要加final修飾符的原理基本講完了。那現(xiàn)在我們來(lái)談一談JDK8對(duì)這一問(wèn)題的新的知識(shí)點(diǎn)。在JDK8中如果我們?cè)谀涿麅?nèi)部類中需要訪問(wèn)局部變量,那么這個(gè)局部變量不需要用final修飾符修飾。看似是一種編譯機(jī)制的改變,實(shí)際上就是一個(gè)語(yǔ)法糖(底層還是幫你加了final)。但通過(guò)反編譯沒(méi)有看到底層為我們加上final,但我們無(wú)法改變這個(gè)局部變量的引用值,如果改變就會(huì)編譯報(bào)錯(cuò)。
有興趣的小伙伴可以關(guān)注博主
Gitee筆記倉(cāng)庫(kù)
GitHub筆記倉(cāng)庫(kù)

浙公網(wǎng)安備 33010602011771號(hào)