在java中使用匿名内部类,您必须将在匿名内部类中使用的封闭类的变量声明为final
。好吧,我知道为什么必须从
"通过制作lastPrice和price final,它们不是真正的变量 不再,但常数。然后编译器可以只替换使用 lastPrice和匿名类中的价格与值 常数(在编译时,当然),你不会遇到问题 不再访问不存在的变量"
这让我感到徘徊,final
关键字的工作方式就像宏在C \ C ++中一样。到目前为止,我正在使用final
变量,每当我尝试修改时他们(不小心)我会收到一个错误,你可以修改它,因为它被声明为final
。但这个替代的东西对我来说并不清楚。
问题:根据上述链接中选择的答案,回答者说
这就是为什么它不起作用的原因:
变量lastPrice和price是main()中的局部变量 方法。使用匿名类创建的对象可能会持续 直到main()方法返回。
当main()方法返回时,局部变量(例如lastPrice和 价格)将从堆栈中清理干净,因此它们不再存在 在main()返回后。
但是匿名类对象引用了这些变量。事情 如果匿名类对象试图访问,将会出现严重错误 清理后的变量。
**
**
答案 0 :(得分:1)
不,这与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
。
在变量为常量的不同情况下,编译器可以在编译时替换该值。但是,这对内部类来说并不特殊,因为常量在编译时被替换,无论 where 使用它们。
故事的寓意是匿名内部类可以访问任何final
变量,无论它是编译时常量还是在运行时计算。
答案 1 :(得分:1)
通过匿名课程,您实际上是在声明一个无名的"嵌套类。对于嵌套类,编译器会生成一个新的独立公共类,其中包含一个构造函数,该构造函数将其用作参数的所有变量(对于"命名为"嵌套类,这始终是原始/封闭类的实例) 。这样做是因为运行时环境没有嵌套类的概念,因此需要从嵌套类到独立类的(自动)转换。
以此代码为例:
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中,它所指向的实例未被修改,指向它的所有其他引用变量(如匿名类中的那个: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);
}
}
像这样,当参考变量“共享”时#39;在EnclosingClass中重新分配,这发生在调用Thread#run()之前,你会看到"其他你好"打印两次,因为现在EnclosingClass $ 1#enclosing变量将保持对声明它的类的对象的引用,因此对EnclosingClass $ 1的实例可以看到对该对象的任何属性的更改。
有关该主题的更多信息,您可以看到这篇优秀的博文(不是我写的):http://kevinboone.net/java_inner.html
答案 2 :(得分:1)
声明最终字段有助于优化器做出更好的优化决策,因为如果编译器知道字段的值不会改变,它可以安全地将值缓存在寄存器中。最终字段还通过让编译器强制字段为只读来提供额外的安全级别。
您可以在此处找到关于关键字final
的完整文章