Speaking as a Java developer who makes all variables final
by default (and who appreciates the fact that Eclipse can do this automatically), I find it easier to reason about my program if variables are initialized once and never changed again.
For one thing, uninitialized variables are no longer any concern, because trying to use a final
variable before it has been initialized will result in a compile error. This is particularly useful for nested conditional logic, where I want to make sure that I covered all the cases:
final int result;
if (/* something */) {
if (/* something else */) {
result = 1;
}
else if (/* some other thing */) {
result = 2;
}
}
else {
result = 3;
}
System.out.println(result);
Did I cover all the cases? (Hint: No.) Sure enough, this code won't even compile.
One more thing: In general, any time you know that something is always true about a variable, you should try to get your language to enforce it. We do this every day when we specify a variable's type, of course: The language will ensure that values that are not of that type cannot be stored in that variable. Likewise, if you know that a variable should not be reassigned because it already has the value that it should keep for the entire method, then you can get the language to enforce that restriction by declaring it final
.
Lastly, there's the matter of habit. Others have mentioned that this is a habit (+1 to Jon for that), but let me say something about why you would want this habit. If you are declaring fields in your class and not local variables in a method, then it's possible for multiple threads to access those fields at the same time. There are some obscure exceptions, but in general, if a field is final, then every thread that uses your class will see the same value for the variable. Conversely, if a field is not final and multiple threads are using your class, you will need to worry about explicit synchronization using synchronized
blocks and/or classes from java.util.concurrent
. Synchronization is possible, but programming is hard enough already. ;-) So, if you just declare everything final
out of habit, then many of your fields will be final and you'll spend as little time as possible worrying about synchronization and concurrency-related bugs.
For more on this habit, check out the "Minimize Mutability" tip in Joshua Bloch's Effective Java.
Edit: @Peter Taylor has pointed out that the example above would also not compile if the final
keyword is removed, which is completely correct. When I advised in favor of keeping all local variables final, it's because I wanted to make examples like the following one impossible:
int result = 0;
// OK, time to cover all the cases!
if (/* something */) {
if (/* something else */) {
result = 1;
}
else if (/* some other thing */) {
result = 2;
}
// Whoops, missed an "else" here. Too bad.
}
else {
result = 3;
}
System.out.println(result); // Works fine!
Using a new variable instead of reusing an old one is how I can tell the compiler that trying to cover the complete universe of possibilities, and using final
variables forces me to use a new variable instead of recycling an old one.
Another valid complaint about this example is that you should avoid complex nested conditional logic in the first place. That's true, of course, precisely because it's hard to make sure you covered all the cases in the way that you intended. However, sometimes complex logic can't be avoided. When my logic is complex, I want my variables to be as simple to reason about as possible, which I can achieve by making sure my variables' values never change after they are initialized.