You're creating a snippet of code which can 'travel'. The code in the {}
that belong to your new Date()
declaration isn't run right where you wrote it; it is attached to this date object you've made, and goes with it. This date object can travel: It can be stored in a field. Maybe it is run 18 days from now, in a completely different thread. The VM has no idea, so it needs to be prepared for that to happen.
So let's say it does: What is to happen to your 'counter' variable?
Normally, local variables are stored 'on the stack' and are destroyed as the method exits. But in that case we'd be destroying a variable that your travelling code still has access to, so what does that mean, 18 days from now, when your date compareTo code is invoked?
Let's say that the VM silently 'upgrades' the variables; instead of declaring it on the stack like normal, it declares it on the heap so that the variable can survive the method exiting.
Allright. What if compareTo is invoked in another thread? Should it now be possible to mark a local variable as 'volatile'? Is it okay to state that, in java, even local variables may show race conditions?
This is a judgemental call; something for the language designers to decide.
Java's language designers decided against silent upgrade into heap and against allowing locals to potentially be subjected to multi-thread access.
Therefore, any local variable that you access in any codeblock that can 'travel'* must either [A] be declared final
or [B] act as if it could have been, in which case java will silently make it final for you.
The change is that counter
, the variable itself, does not change in the second snippet: it is a reference to an array, and that reference never changes. Effectively you've added the level of indirection and the heap access yourself: arrays exist on the heap.
For what its worth, I find usage of AtomicX more readable. So if you need an int that is modifyable in traveling code, don't do new int[1]
; do new AtomicInteger
. If you need a modifiable string, use new AtomicReference<String>()
, not new String[1]
.
NB: Yes, in this specific code, the counter variable is only used, even by the sort op, within this method and the counter var can go away once this method ends, but the compiler doesn't do that kind of extremely in depth analysis to figure that out, it uses the much simpler rule of: Wanna access a local var from outer scipe within 'travelling' code? Not allowed - unless it is (effectively) final.
*) Traveling code is anything inside a method local or anonymous class definition, and anything in a lambda. So:
void method() {
class MethodLocalClassDef {
// anything here is considered 'travelling'
}
Object o = new Object() {
// this is an anonymous class def,
// and anything in here is 'travelling'
};
Runnable r = () -> {
// this is a lambda, and considered 'travelling'
};
}