Reputation: 3532
I understand the whole idea of Java not supporting closure, but then I ran into this behavior:
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
}
My co-worker gets an error on the first implementation as well, but I don't? I'm running JRE 1.8.0_45, he is running 1.8.0_31
Basically, all I want is to make sure that all the sockets are listening for clients. I actually wait for all sockets to connect before I do anything, thus the fact that the variable is not final is not really relevant.
Upvotes: 0
Views: 82
Reputation: 122518
In the first case, server
can be captured in Java 8 because it is "effectively final" -- nothing ever assigns to it after its initialization. Basically, in a for-each loop, the iteration variable is scoped locally to each iteration. Each iteration, you get a new server
variable. So even though the server
is different in different iterations, it's not a change in the value of a variable, but instead different variables.
In the second case, i
cannot be captured because it is not "effectively final" -- the i++
expression assigns to the variable i
after its initialization. The difference between this and the first case is that this is a normal for-loop. Variables declared in a normal for loop are scoped to the whole loop. You just get one variable, that is kept (and usually updated) across iterations. You update i
between iterations by incrementing it. But maybe you could have done some more exotic updating operation between iterations. The full flexibility of for loops makes it impossible in general to scope the variable to the inside of each loop, even though in this case you are effectively having one value for each iteration.
If, on the other hand, you assigned the value of i
to a local variable inside the loop, that variable would be scoped to the inside of the iteration, and since you don't assign to it subsequently, it would be "effectively final" and can be captured:
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();
}
}
}
About the error for the first case, it sounds like that situation in pre-Java-8. In pre-Java-8, captured variables must be explicitly declared final
. Since server
is not explicitly declared final
, it could not be captured in a local or anonymous class in Java 7 and before (even though it is "effectively final", meaning that it would compile if declared final
). Java 8 relaxed this to "effectively final". Now, you said your co-worker is also using Java 8. I can only guess that this is either wrong information or a bug in his compiler version.
Upvotes: 1
Reputation: 65879
Your problem is that i
goes out of scope and changes while the server
reference is effectively final.
Here it is fixed.
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
Note that you cannot do:
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);
}
}
});
}
The reason behind all of this is that a lambda is not the object, it is really just enough instructions and environment for the JVM to create the object when it feels like it. If you access a local variable inside a lambda it must be effectively final.
Upvotes: 0