Why are local variables not initialized in Java?
Was there any reason why the designers of Java felt that local variables should not be given a default value? Seriously, if instance variables can be given a default value, then why can't we do the same for local variables?
And it also leads to problems as explained in this comment to a blog post:
Well this rule is most frustrating when trying to close a resource in a finally block. If I instantiate the resource inside a try, but try to close it within the finally, I get this error. If I move the instantiation outside the try, I get another error stating that a it must be within a try.
Very frustrating.
Solution 1:
Local variables are declared mostly to do some calculation. So it's the programmer's decision to set the value of the variable and it should not take a default value.
If the programmer, by mistake, did not initialize a local variable and it takes a default value, then the output could be some unexpected value. So in case of local variables, the compiler will ask the programmer to initialize it with some value before they access the variable to avoid the usage of undefined values.
Solution 2:
The "problem" you link to seems to be describing this situation:
SomeObject so;
try {
// Do some work here ...
so = new SomeObject();
so.DoUsefulThings();
} finally {
so.CleanUp(); // Compiler error here
}
The commenter's complaint is that the compiler balks at the line in the finally
section, claiming that so
might be uninitialized. The comment then mentions another way of writing the code, probably something like this:
// Do some work here ...
SomeObject so = new SomeObject();
try {
so.DoUsefulThings();
} finally {
so.CleanUp();
}
The commenter is unhappy with that solution because the compiler then says that the code "must be within a try." I guess that means some of the code may raise an exception that isn't handled anymore. I'm not sure. Neither version of my code handles any exceptions, so anything exception-related in the first version should work the same in the second.
Anyway, this second version of code is the correct way to write it. In the first version, the compiler's error message was correct. The so
variable might be uninitialized. In particular, if the SomeObject
constructor fails, so
will not be initialized, and so it will be an error to attempt to call so.CleanUp
. Always enter the try
section after you have acquired the resource that the finally
section finalizes.
The try
-finally
block after the so
initialization is there only to protect the SomeObject
instance, to make sure it gets cleaned up no matter what else happens. If there are other things that need to run, but they aren't related to whether the SomeObject
instance was property allocated, then they should go in another try
-finally
block, probably one that wraps the one I've shown.
Requiring variables to be assigned manually before use does not lead to real problems. It only leads to minor hassles, but your code will be better for it. You'll have variables with more limited scope, and try
-finally
blocks that don't try to protect too much.
If local variables had default values, then so
in the first example would have been null
. That wouldn't really have solved anything. Instead of getting a compile-time error in the finally
block, you'd have a NullPointerException
lurking there that might hide whatever other exception could occur in the "Do some work here" section of the code. (Or do exceptions in finally
sections automatically chain to the previous exception? I don't remember. Even so, you'd have an extra exception in the way of the real one.)
Solution 3:
Moreover, in the example below, an exception may have been thrown inside the SomeObject construction, in which case the 'so' variable would be null and the call to CleanUp will throw a NullPointerException
SomeObject so;
try {
// Do some work here ...
so = new SomeObject();
so.DoUsefulThings();
} finally {
so.CleanUp(); // Compiler error here
}
What I tend to do is this:
SomeObject so = null;
try {
// Do some work here ...
so = new SomeObject();
so.DoUsefulThings();
} finally {
if (so != null) {
so.CleanUp(); // safe
}
}
Solution 4:
Notice that the final instance/member variables don't get initialized by default. Because those are final and can't be changed in the program afterwards. That's the reason that Java doesn't give any default value for them and force the programmer to initialize it.
On the other hand, non-final member variables can be changed later. Hence the compiler doesn't let them remain uninitialised, precisely, because those can be changed later. Regarding local variables, the scope of local variables is much narrower. The compiler knows when it's getting used. Hence, forcing the programmer to initialize the variable makes sense.