4

So, here's what I understand: Java doesn't support closures, so it sort of copies the variables from the containing scope into the nested scope, so they are available later. Because this is a copy, there is no way to synchronize the original and the copy, and the variable is forced to be final so the developer cannot change it and expect it to be updated. This understanding is partly taken form these answers

And that brings us to this code working:

public class SimpleClosure {

    public static void main(String[] args) {
        new SimpleClosure().doStuff();
    }

    public void doStuff() {
        final int number = 3;

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Integer.toString(number));
            }
        }.start();
    }
}

So, great. Now, the thing is, a final modifier only prevents me from changing the object that the variable points to, but I can change the object without problems. If a "copy" was made, then changes to the object contents should not be reflected. The question, therefore is, why does the following code work?

import java.util.HashMap;
import java.util.Map;

public class StretchingClosure {

    public static void main(String[] args) {
        new StretchingClosure().doStuff();
    }

    public void doStuff() {
        final Map<String, String> map = new HashMap<String, String>();
        map.put("animal", "cat");

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(map.get("animal")); // Outputs dog See (1)
            }
        }.start();
        map.put("animal", "dog");
    }
}

Needless to say, I'm missing something, or I'm oversimplyfying the way the compiler handles these cases. Could anyone please enlighten me?

(1): as pointed out by @trashgod, the output is true most of the time on most of the platforms, but it's not guaranteed due to the lack of synchronization. This is good enough for the example, but bad practice in general.

Community
  • 1
  • 1
Miquel
  • 15,405
  • 8
  • 54
  • 87
  • 1
    "Java doesn't support closures" What do you mean "closures"? Anything that has access to variables in the surrounding scope is a "closure". Whether it captures variables by value or by reference, or whether those variables are assignable, are separate issues. – newacct Jan 25 '13 at 19:25
  • @newacct well, I thought it was widely agreed that Java doesn't support closures, but just an approximation to them using anonymous classes. Variables referenced within the inner class must be specifically marked (with `final`) and their values are not kept up to date in and outside the block (only the content, but not the reference can be changed. This is also implicit to the way java handles variables). [This](http://www.ibm.com/developerworks/java/library/j-jtp04247/index.html) might explain it better than me. – Miquel Jan 26 '13 at 09:19

4 Answers4

4

Don't confuse the variable with the object: the reference from the local variable is indeed copied, but it still refers to the same object, in your case the map. There is a widely known idiom to work around the final restriction, involving arrays:

final int[] x = {1};
... use in an anonymous instance...
System.out.println(x[0]);
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • You're absolutely right. This makes sense now. As to the idiom, I understand you're changing the value of x in the anonymous instance, and then it's no longer 1 after it, right? Neat trick (yet very wrong ;) – Miquel Jan 25 '13 at 13:42
  • Yes, you use `x[0]` as you would otherwise use just `x`. – Marko Topolnik Jan 25 '13 at 13:56
1

Java does the same thing there as it does with regular method parameters:

Method parameters are passed by reference value, so while you cannot change the object itself, if it is mutable and provides ways of mutating its inner state, you can change that state. You cannot change a string, but you can change the items inside a collection, for example.

The reference is passed by value = the reference is copied. The object itself isn't.

eis
  • 51,991
  • 13
  • 150
  • 199
1

The comment // Outputs dog is misleading in the sense that it's only true most of the time on most platforms. One second is plenty of time to update the Map on the initial thread, but nothing guarantees visibility of the updated value in the anonymous thread unless access to the shared data is correctly synchronized. See Memory Consistency Properties for a nice summary of relevant features in java.util.concurrent.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 1
    Fair enough :) I was only making a quick example, but being strict, you're right. I'm adding it to the question as a note, and thanks! – Miquel Jan 25 '13 at 13:43
0

Anonymous classes do not get copies of variables, but rather copies of references to the objects, that's why after 1s you get the "right" value which was changed outside the anonymous class.

Bastien Jansen
  • 8,756
  • 2
  • 35
  • 53
  • 1
    They do get copies. At runtime, the value is stored in a compiler-generated member variable of the anonymous class. That don't mean that a reference copy wouldn't still point to the actual object. – eis Jan 25 '13 at 13:45
  • I think the inner behaviour is not really important, the point is that in the inner class it is as if we have a reference that can't be reassigned, but the object in itself can be modified (and modifications will be available outside of the inner class). But thanks for the precision though, it's useful for my personal knowledge :) – Bastien Jansen Jan 25 '13 at 13:55
  • @eis well, it cannot be copied, since a copy of the map would point to the String "cat" instead of "dog" – Miquel Jan 25 '13 at 14:26
  • 1
    He means the reference is copied, not the object being referenced. The two references point to the same object. – Bastien Jansen Jan 25 '13 at 14:26