為什么內(nèi)部類引用外部類的局部變量時(shí)改含,此變量要用final修飾
如圖
public void test() {
final int i = 3;
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvShow.setText(String.valueOf(i));
}
});
}
上面的代碼是使用了匿名內(nèi)部類的方式。Runnable是一個(gè)接口,此內(nèi)部類實(shí)現(xiàn)了Runnable方法禽翼,重寫了接口里面的run()方法,方法內(nèi)引用了外部方法體內(nèi)的局部變量 i,這個(gè)時(shí)候i只能被定義為final變量闰挡,否則編譯會(huì)報(bào)錯(cuò)锐墙。
我們可以從JVM的角度去解釋這個(gè)現(xiàn)象,在編譯期的時(shí)候长酗,所有的類都會(huì)被編譯成Class文件溪北。匿名內(nèi)部類也會(huì)被編譯成Class文件。但是上面的例子的內(nèi)部類編譯會(huì)和我們所知道的普通類編譯方式會(huì)有些不同夺脾。
大多數(shù)的類在編譯的時(shí)候刻盐,需要知道每個(gè)方法需要為其所有的局部變量分配多少內(nèi)存。所以它會(huì)去檢查方法內(nèi)定義的變量劳翰,從而確定此方法到真正運(yùn)行的時(shí)候需要在棧中開辟多少內(nèi)存敦锌。但這只是計(jì)算需要多少內(nèi)存,真正分配內(nèi)存是在運(yùn)行期佳簸。
所以可以發(fā)現(xiàn)匿名內(nèi)部類不同的是乙墙,雖然方法中的i沒(méi)有定義,但是在編譯期會(huì)給它分配額外的內(nèi)存生均,并給它賦與外部的i同一個(gè)值听想。但是此變量已經(jīng)不是外部的局部變量了。在內(nèi)存的角度上看马胧,匿名內(nèi)部類的i實(shí)際上不是外部的i汉买,它們使用的內(nèi)存空間都不同,只是它們的值相同佩脊。
其實(shí)本質(zhì)上來(lái)說(shuō)蛙粘,完全可以當(dāng)作兩個(gè)不同的變量去使用,但是Java的設(shè)計(jì)人員可能想要保持一致性威彰,因?yàn)镴ava的初學(xué)者在不了解其中真正的機(jī)制的時(shí)候出牧,會(huì)以為他們就是同一個(gè)變量,所以干脆就把變量強(qiáng)制定義為final歇盼,這樣變量就不能被重新賦值舔痕,營(yíng)造一種他們是同一個(gè)變量的“假象”。
為什么引用外部類的成員變量的時(shí)候豹缀,又不用final修飾呢伯复?
有一點(diǎn)要注意,當(dāng)引用的不是局部變量而是外部類的成員變量的時(shí)候邢笙,不一樣要用final修飾啸如,因?yàn)樗恍枰裆厦嬲f(shuō)的那樣需要在棧中重新開辟一個(gè)空間,而是內(nèi)部類持有外部類的引用鸣剪,可以直接引用外部類的成員變量组底。
與C/C++的不同
在C++中丈积,同樣的現(xiàn)象,引用外部類的局部變量時(shí)债鸡,是不用加final了江滨。其根本原因是出在,C++在編譯期的時(shí)候就進(jìn)行動(dòng)態(tài)連接了厌均,而Java是在運(yùn)行期的時(shí)候才進(jìn)行動(dòng)態(tài)連接唬滑。
說(shuō)白了,就是C++的編譯時(shí)就已經(jīng)分配好了空間棺弊,自然外部類和匿名內(nèi)部類的i其實(shí)用的是同一塊內(nèi)存區(qū)域晶密,是真正意義上的同個(gè)變量,所以不需要加final模她。