3

We all know you can't do things like this:

int a = 7;
new Runnable() {
     public void run() {
         System.out.println(a);
     }
}.run();
...

...without making a final. I get the technical reason why and it's because local variables live on the stack and you can't safely make a copy unless you know it won't change.

What I struggle to see however is why the compiler doesn't have an implementation hack so that it when it sees the above situation it compiles down to something like:

int[] a = {7};
new Runnable() {
    public void run() {
        System.out.println(a[0]);
    }
}.run();
...

Then we're in the position where it's safe to access a from an anonymous inner class and indeed change it if we wish. Of course, it might only do this hack when we actually change a. As far as I could see this would be a relatively simple thing to put in, would work for all types and would allow a to be changed from whatever context. Of course, the above proposal could be changed to use synthetic wrapper classes for multiple values or another approach that's a bit more efficient, but the idea is the same. I guess there's a small performance hit, but I doubt it'd be excessive especially with the potential for more optimisations under the hood. Aside from perhaps certain reflective calls that rely on synthetic fields being a certain way breaking, I can't see many disadvantages, but I've never heard it seriously proposed! Is there a reason why?

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
  • the `final` variables are actually instance fields of the inner class (there are no inner classes pe SE, just the compile generates something that resembles them). Why note using an an array - it won't be compatible w/ serialization and many other things. – bestsss Jan 20 '12 at 14:57
  • Another problem you have (and can have in groovy which doesn't have this restriction) is that non final variables can change (otherwise you wouldn't have a problem making it final) int a = 1; // use a in another thread. a = 2; Should the thread always see a = 1 or can it sometimes see a = 2. – Peter Lawrey Jan 20 '12 at 15:05
  • 2
    Don't you hate it when someone closes a question you are in the middle of answering. :( – Peter Lawrey Jan 20 '12 at 15:06
  • @PeterLawrey If there's another point you'd care to make in a comment I'd be interested in hearing it - personally I think it was a perfectly valid question, but hey-ho... – Michael Berry Jan 20 '12 at 15:09
  • Its hard to write formatted code in comments. – Peter Lawrey Jan 20 '12 at 15:44
  • @PeterLawrey This is true, though it appears the question has now been reopened ;) – Michael Berry Jan 21 '12 at 19:24

3 Answers3

2

When the anonymous inner class is constructed, the values of all the variables used within it are copied. So if the inner class then tried to change the value of the variable, that wouldn't be visible. For example, suppose this were valid:

int a = 7;
Runnable r = new Runnable() {
    public void run() {
        a = 5;
    }
};
r.run();
System.out.println(a);

You might expect it to print 5 (which indeed it would in C#). But because only a copy has been taken, it would actually print 7... if it were allowed, with no bigger changes.

Of course, Java could have been changed to really capture the variable instead of its value (as C# was for anonymous functions). That requires automatically creating an extra class to store the "local" variables, and make both the method and the anonymous inner class share an instance of that extra class. That would have made anonymous inner classes more powerful, but arguably harder to understand. C# decided to go for the power-but-complexity route; Java went for the restrictive-but-simple approach.

(Using an array instead of a custom class is valid for a single variable, but becomes more wasteful when there are multiple variables involved - you don't really want to have to create a wrapper object for every variable if you can help it.)

Note that there are significant complexities involved in the capture-the-variable approach, at least using the C# rules. For example:

List<Runnable> runnables = new ArrayList<Runnable>();
int outer = 0;
for (int i = 0; i < 10; i++) {
    int inner = 0;
    runnables.add(new Runnable() {
        public void run() {
            outer++;
            inner++;
        }
    });
}

How many "inner" variables are created? One for each instance of the loop, or one overall? Basically, scopes make life tricky for this sort of thing. Feasible, but tricky.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    The poster explains in his question that he understands why they're final, and proposes using single-element arrays as a solution for reference variables, asking why it wasn't done that way. – Ernest Friedman-Hill Jan 20 '12 at 14:45
  • Why the downvote? This is a correct answer to a question with a lot of inaccuracies in it. @ErnestFriedman-Hill - the OP doesn't appear to understand why reference local variables ***really*** have to be final. – Perception Jan 20 '12 at 14:45
  • And then it was edited to add the last bit which actually answers the question, so I removed the downvote. – Ernest Friedman-Hill Jan 20 '12 at 14:50
  • I really do understand why local variables have to be final - at least with the current implementation, as I stated in the question! As you point out (and as C# shows) they don't *have* to be that way at all though, it's an implementation decision. So I guess it boils down to Java chose to keep the implementation simple? – Michael Berry Jan 20 '12 at 15:06
  • @berry120: Not just *implementation* - it keeps the *language* simple. In various cases, Java has chosen simplicity over power. It's a reasonable design decision - not the one I prefer in this case, but reasonable in itself. – Jon Skeet Jan 20 '12 at 15:07
  • Perhaps I'm viewing it from the wrong angle, but on the surface I actually think it makes it more complex - I find not being able to change local variables in that way much less intuitive. It all makes sense when you think about the implementation behind it of course, but I class that as an implementation detail rather than a language complexity issue. Guess it depends where you draw the line and how you see things! – Michael Berry Jan 20 '12 at 15:14
  • @berry120: There's one simple rule which is easy to follow but restrictive. The language stops you from getting into complex situations which might behave surprisingly - the only surprise you might get is a compile-time failure, but that's less dangerous than an execution-time surprise of behaving differently to expectations. That's definitely *not* an implementation detail. The C# language spec has to go to some lengths to specify the behaviour everywhere - and C# 5 is going to get a change to the behaviour around `foreach` because it's caused so much trouble. – Jon Skeet Jan 20 '12 at 15:20
  • @berry, your example uses `Runnable{...}.run() ` which is the uncommon case, most runnables are executed in a different thread/context. Now to apply changes and keep visibility (baring the confusion) to the variables it'd take some `volatile` that turns into a truly awkward local variable. I see the anon. classes a [leak-prone] convenience which I prefer avoiding. They have no sensible names making profiling harder. (looking at the output of jmap -histo full of `$2$1$4` names must be truly exciting). Is short I approval using `final` and consider it a good design decision. – bestsss Jan 20 '12 at 16:59
  • @bestsss Fair enough, runnable perhaps wasn't the best thing to use - but the case applies in general, not just in different threaded contexts. – Michael Berry Jan 20 '12 at 17:06
1

Another problem you have (and can have in groovy which doesn't have this restriction) is that non final variables can change (otherwise you wouldn't have a problem making it final)

int a = 1; 
// use a in another thread, or potentially run it later
a = 2; 

Should the thread always see a = 1 or can it sometimes see a = 2.

The only way to write predictable anonymous classes is if the local variables don't change. The compiler could be smarter and detect when a variable can no longer change and that would simplify some odd cases IMHO. But the simplest solution is to require the local variable to be final.

BTW: An alternative which my IDE has an auto-fix for is to use an array of one instead.

final int[] a = { 1 };
//  use a[0] in an anonymous class.
a[0] = 2;

While ugly, it does make it unlikely you will accidentally write an anonymous class where the value could change.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
0

Given that anonymous inner classes can outlive the scope in which they were created, I don't see how you're 'hack' would be easy to implement. The inner class still needs to maintain copies of all the variables it has closed around, and as such they cannot refer to variables that have been created on the stack.

Perception
  • 79,279
  • 19
  • 185
  • 195
  • 2
    Arrays - or any kind of object - are (at least conceptually) always allocated on the heap and thanks to the GC the scope "problem" isn't really problematic here. – Voo Jan 20 '12 at 15:32
  • @Voo - correct, but it would be brutally hackish to internally represent all a method's primitive values as arrays, just so that they could be closed upon in anonymous classes. Reading through how C# does it, thats a much more elegant solution, but much more complex, as others have pointed out. – Perception Jan 20 '12 at 15:40