Java 方法中最终变量声明的效果如何?
简单服务器的经典示例:Java 方法中最终变量声明的效果如何?,java,concurrency,methods,final,Java,Concurrency,Methods,Final,简单服务器的经典示例: class ThreadPerTaskSocketServer { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { final Socket connection = socket.accept(); Runnabl
class ThreadPerTaskSocketServer {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable task = new Runnable() {
public void run() {
handleRequest(connection);
}
};
new Thread(task).start();
}
}
}
为什么要将
套接字
声明为最终
?这是因为处理请求的新线程可能会引用方法中的套接字变量,并导致某种并发修改异常?您需要声明它为final,而不仅仅是应该。否则,编译器将无法在匿名可运行类实现中使用它。局部变量不会在线程之间共享。(局部变量是激活记录的一部分,每个线程都有自己的激活记录)
由于connection
是一个局部变量,因此无法在线程之间共享它。由于它不是在线程之间共享的,所以您需要将它设置为final,因此它是否是局部变量并不重要(它更像是一个常量)。在这种情况下,变量必须是final,才能在匿名可运行的
实现中使用
这是因为当变量已经超出范围并因此消失时,该对象将存在。对象获取变量的副本。为了隐藏这一点,变量必须是final,这样就没有人可以期望一个副本中的更改对另一个副本可见。它不是为了解决ConcurrentModificationException。方法嵌套类(如匿名内部类)中使用的任何局部变量都必须声明为final。请参见上周的类似讨论:
实际上,对于线程,这里对线程安全性的贡献很小;线程之间的最终变量将没有可见性问题。但是,这根本不能保证线程安全。请考虑以下示例:
class A {
B foo() {
final C c;
return new B() {
void goo() {
// do something with c
}
}
}
}
// somewhere else in the code
A a = new A();
B b = a.foo();
b.goo();
如果c不是final,当您到达b.goo()
时,它将指向垃圾,因为c将被垃圾收集—在方法调用结束后是一个局部变量。声明方法变量final意味着它的值不能更改;只能设置一次。这在这种情况下如何适用
我知道匿名类的这种限制已经有一段时间了,但我一直不太明白为什么。从目前的回答来看,没有其他人真正做到这一点。一些谷歌搜索出现在下面,我认为这很好地解释了这一点
匿名本地类可以使用本地
因为编译器
自动给班级a
保存副本的专用实例字段
类使用的每个局部变量的。
编译器还添加了隐藏的
每个构造函数的参数
初始化这些自动创建的
私人领域。因此,本地类
实际上不访问本地
变量,但仅仅是它自己的私有
它们的副本。只有这样才能
正确工作是如果本地
变量被声明为final,因此
他们保证不会改变。
有了这一保证
本地类确信其
变量的内部副本
准确反映当地的实际情况
变量
归功于:
当然不明显,我认为编译器确实应该对开发人员隐藏一些东西。您是否暗示最终变量不会被垃圾收集?这是错误的。问题不在于垃圾收集。内部类无论如何都有一个对变量的强引用。语言设计者可以允许在内部类中使用非最终局部变量。但是,这可能会让开发人员感到困惑(请参阅Michaels response),因为不清楚引用的赋值是否被内部类看到。@Pindatjuh-我的意思是,当方法结束时,最终变量不会被垃圾收集@Eyal-你是对的,语言本来可以设计得更好,等等。但是,当前的设计就是我们所拥有的……它们只有在引用计数为零时才是GCed,这可能是在方法的末尾。在您的示例中,仅当返回的B
为时,c
才为GCed,因为goo()
中存在引用可能性。另外,c
需要初始化为某个值(null或instance),因为final
变量没有默认值。答案很好,有点技术性。我想补充一下为什么它需要这种保证:因为内部类可能是GCed的,如果局部变量以后发生更改,那么更改需要反映到内部类实例中,这就是GCed:如果变量是final,就避免了一个问题。声明方法变量final意味着它的值不能更改;只能设置一次
。不是真的。这意味着对对象的引用不能更改。该值仍然可以更改多次。(除非对象是基本体)