我理解Java不支持闭包的整个想法,但后来我遇到了这种行为:
class myClass
{
static List<ServerSocket> servers;
//assume the list contains a bunch of items
void myFunc()
{
//This looks fine - no errors
for (ServerSocket server : servers)
{
new Thread(new Runnable())
{
public void run()
{
server.accept();
}
}
}
//This results in an error
for (int i=0; i < servers.size(); i++)
{
new Thread(new Runnable())
{
public void run()
{
//Error here - i is not final
servers.get(i).accept();
}
}
}
}//end myFunc
}
我的同事也在第一次执行时遇到错误,但我没有?我正在运行JRE 1.8.0_45,他正在运行1.8.0_31
基本上,我想要的是确保所有套接字都在监听客户端。在我做任何事情之前,我实际上都在等待所有套接字连接,因此变量不是final的事实并不是真正相关的。
答案 0 :(得分:1)
在第一种情况下,可以在Java 8中捕获server
,因为它是有效的最后一个&#34; - 初始化后没有任何东西可以分配给它。基本上,在for-each循环中,迭代变量在本地范围内限定为每次迭代。每次迭代,您都会得到 new server
变量。因此,即使server
在不同的迭代中不同,它也不是变量值的变化,而是变量的变化。
在第二种情况下,i
无法捕获,因为它不是&#34;有效的最终&#34; - i++
表达式在初始化后分配给变量i
。这与第一种情况的区别在于这是一个正常的for循环。在正常for循环中声明的变量将作用于整个循环。您只需获得一个变量,该变量在迭代中保留(并且通常更新)。您可以通过递增来在迭代之间更新i
。但也许你可以在迭代之间做一些更奇特的更新操作。 for循环的完全灵活性使得通常无法将变量范围限定在每个循环的内部,即使在这种情况下,您实际上每次迭代都有一个值。
另一方面,如果您将i
的值赋给循环内的局部变量,那么该变量的范围将限定在迭代的内部,因为您不会分配给随后,它将是“有效的最终”#34;并且可以被捕获:
for (int i=0; i < servers.size(); i++)
{
int j = i; // j is effectively final
new Thread(new Runnable())
{
public void run()
{
servers.get(j).accept();
}
}
}
关于第一种情况的错误,听起来像Java-8之前的情况。在Java-8之前,必须显式声明捕获的变量final
。由于server
未明确声明为final
,因此无法在Java 7及之前的本地或匿名类中捕获它(即使它是&#34;实际上是最终的&#34;,意味着它如果声明final
,则会编译。 Java 8放宽了这个有效的最终版本#34;现在,你说你的同事也在使用Java 8.我只能猜测这是错误的信息或他的编译器版本中的错误。
答案 1 :(得分:0)
您的问题是i
超出了范围并且更改了server
引用实际上是最终的。
此处已修复。
static List<ServerSocket> servers;
//assume the list contains a bunch of items
void myFunc() {
//This looks fine - no errors
for (ServerSocket server : servers) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
server.accept();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
});
}
//This is now ok too.
for (int i = 0; i < servers.size(); i++) {
final ServerSocket socket = servers.get(i);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
//Error here - i is not final
socket.accept();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
});
}
}//end myFunc
请注意,您无法执行以下操作:
for (int i = 0; i < servers.size(); i++) {
Thread t = new Thread(new Runnable() {
final ServerSocket socket = servers.get(i);
@Override
public void run() {
try {
//Error here - i is not final
socket.accept();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
});
}
所有这一切背后的原因是lambda不是对象,它实际上只是足够的指令和环境,让JVM在感觉到它时创建对象。如果你访问lambda中的局部变量,必须是最终的。