[英]final in anonymous classes,just like macros in C++?
在Java中使用匿名內部類時,必須將在匿名內部類中使用的封閉類的變量聲明為final
。 好吧,我明白了為什么必須從
“通過將lastPrice和price最終定為變量,它們不再是真正的變量,而是常量。然后,編譯器可以將常量類的值替換為匿名類中對lastPrice和price的使用(當然是在編譯時),然后您不再會訪問不存在的變量的問題”
這使我無所適從,這是final
關鍵字的工作方式,就像C \\ C ++中的Macros一樣。到現在為止,我使用final
for變量的方式是每當我嘗試修改它們時(偶然),我都會得到一個錯誤您無法修改它,因為它被聲明為final
。但是對我來說,這種替換的事情還不清楚。
問題:根據上面鏈接中選擇的答案,回答者說
這就是為什么它不起作用的原因:
變量lastPrice和price是main()方法中的局部變量。 您使用匿名類創建的對象可能會持續到main()方法返回之后。
當main()方法返回時,將從棧中清除局部變量(例如lastPrice和price),因此在main()返回之后它們將不再存在。
但是匿名類對象引用了這些變量。 如果匿名類對象在清除變量后嘗試訪問變量,則事情將變得非常糟糕。
**
**
不,這不像C ++中的宏。 不同之處在於宏是在編譯時評估的,而預處理器則用其定義替換宏。
另一方面, final
變量可以在運行時計算。 但是,一旦設置,該值將無法在以后更改。 該約束使得可以在內部類中使用該值。
讓我們看一個例子,使它更清楚:
public void func(final int param) {
InnerClass inner = new InnerClass() {
public void innerFunc() {
System.out.println(param);
}
}
inner.innerFunc();
}
請注意,可以在運行時通過向其傳遞不同的值來設置param
。 但是每次func()
,都會創建一個新的InnerClass
對象,並捕獲param
的當前值,由於它被聲明為final
,因此保證永遠不會更改。
在變量恆定的另一種情況下,編譯器可以在編譯時替換該值。 但是,這對於內部類而言並不特殊,因為無論在何處使用常量,常量都會在編譯時替換。
這個故事的寓意是,匿名內部類可以訪問任何final
變量,無論它是編譯時常量還是在運行時計算。
對於匿名類,實際上是在聲明“無名”嵌套類。 對於嵌套類,編譯器將使用構造函數生成一個新的獨立公共類,該構造函數將使用它用作參數的所有變量(對於“命名”嵌套類,這始終是原始/封閉類的實例)。 這樣做是因為運行時環境沒有嵌套類的概念,因此需要從嵌套類到獨立類的(自動)轉換。
以下面的代碼為例:
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
那是行不通的,因為這是編譯器在后台執行的操作:
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
原始的匿名類被編譯器生成的一些獨立類代替(代碼不准確,但應該可以帶給您一個好主意):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
如您所見,獨立類保留對共享對象的引用,請記住,java中的所有內容都是按值傳遞的,因此即使EnclosingClass中的引用變量“ shared”被更改,其指向的實例也不會被修改,以及指向它的所有其他參考變量(例如匿名類中的Enclosing $ 1)將不會意識到這一點。 這是編譯器強迫您將“共享”變量聲明為final的主要原因,這樣,這種類型的行為就不會使其融入已經運行的代碼中。
現在,這是在匿名類中使用實例變量時發生的事情(這是解決問題,將邏輯移至“實例”方法或類的構造函數時應采取的措施):
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
這樣編譯就可以了,因為編譯器將修改代碼,因此新生成的類Enclosing $ 1將保存對實例化該實例的EnclosingClass實例的引用(這只是一個表示,但應該可以使您前進):
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
像這樣,當EnclosingClass中的參考變量“ shared”被重新分配,並且發生在調用Thread#run()之前,您會看到“ other hello”打印了兩次,因為現在EnclosingClass $ 1#enclosing變量將保留一個參考。到聲明該類的對象的對象,因此對該對象上任何屬性的更改對於EnclosingClass $ 1的實例都是可見的。
有關該主題的更多信息,您可以查看以下出色的博客文章(不是我寫的): http : //kevinboone.net/java_inner.html
來自Brian Goetz的@Butterflow:
聲明最終字段有助於優化器做出更好的優化決策,因為如果編譯器知道該字段的值不會改變,則可以安全地將該值緩存在寄存器中 。 final字段還通過使編譯器強制字段為只讀來提供額外的安全性。
您可以在此處找到有關關鍵字final的完整文章
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.