Note: This article applies to Java 6 and earlier versions of Java
Java 7 has introduced the try-with-resources statement which, whilst still requiring resources be closed,
makes the exception handling much easier
Using the 'finally' Clause to Close IO Streams
This Q&A article attempts to explain why it is important to use the finally clause to close
IO streams, although it equally applies to any objects that reserve non-memory system resources.
Why do we have to close an IO stream?
When an IO Stream is opened system resources are reserved and these resources are only released
when the IO Stream is closed or garbage collected. Despite what is said on a number of sites
it should never be left to the garbage collector to release reserved
resources. You, as the programmer, have no control as to when the garbage collector runs or if it ever runs
and you may run out of resources before it does run.
Remember, calling System.gc() does not
actually run the garbage collector it is just a suggestion to the JVM that it may be appropriate
to perform garbage collector. The JVM may or may not actually run the garbage collector.
So, in short, you should consider it your responsibility to close any IO Streams you open.
Why can't we just call the IO Stream's close method?
You could write the following code and you may even see code like this in examples:
public String readFile(File file) throws FileNotFoundException, IOException
{
BufferedReader in = new BufferedReader(new FileReader(file));
StringBuilder text = new StringBuilder();
// read the first line
String line = in.readLine();
// continue to read lines whilst they are available
while ( line != null )
{
text.append(line);
line = in.readLine();
}
// close the stream
in.close();
return text.toString();
}
|
The problem here is that if an exception is thrown during the read loop the method will exit
without closing the stream and hence without releasing the file system resources.
How do we make sure the close method is called?
If we wrap the code in a try-catch-finally statement and place the call to the close method
in the finally clause we can ensure it always runs regardless of how the method exits. For example:
|
public String readFile(File file) throws FileNotFoundException, IOException
{
BufferedInputStream in = null;
try {
StringBuilder text = new StringBuilder();
in = new BufferedReader(new FileReader(file));
// read the first line
String line = in.readLine();
// continue to read lines whilst they are available
while ( line != null )
{
text.append(line);
line = in.readLine();
}
return text.toString();
}
finally
{
// close the stream
in.close();
}
}
|
Now whilst this approach appears to solve the problem there is a flaw.
If the creation of the input stream fails for example by throwing a FileNotFoundException
then when the finally clause executes it will call the close method on 'in'. But the variable
'in' will be null and so a NullPointerException will be thrown masking
the original problem and almost
certainly evading any FileNotFoundExcpetion handling in the calling method.
Fortunately the answer is fairly easy, check to see if 'in' is null before trying to close it.
|
public String readFile(File file) throws FileNotFoundException, IOException
{
BufferedInputStream in = null;
try {
StringBuilder text = new StringBuilder();
in = new BufferedReader(new FileReader(file));
// read the first line
String line = in.readLine();
// continue to read lines whilst they are available
while ( line != null )
{
text.append(line);
line = in.readLine();
}
return text.toString();
}
finally
{
// close the stream
if ( in != null )
in.close();
}
}
|
This approach works well when working with a single resource, unfortunately
the problem is more complicated when multiple resources are involved.
The following mistake can be seen in many examples and whilst the code initially looks
correct it has a major flaw:
|
public static boolean compareFile(File file1, File file2) throws FileNotFoundException, IOException
{
BufferedInputStream in1 = null;
BufferedInputStream in2 = null;
try {
in1 = new BufferedInputStream(new FileInputStream(file1));
in2 = new BufferedInputStream(new FileInputStream(file2));
// compare the files
boolean result = true;
int i1, i2;
do {
i1 = in1.read();
i2 = in2.read();
result = (i1 == i2);
}
while ( result && i1 != -1 && i2 != -1);
return result;
}
finally
{
// close the streams
if ( in1 != null )
in1.close();
if ( in2 != null )
in2.close();
}
}
|
The problems here is the close method can itself throw an exception and if this happens when closing
the first stream, the second stream will not be closed.
So how do we make sure the second close method is always called?
The answer is simply to use another try-catch-finally statement around the first call to the close method.
|
public static boolean compareFile(File file1, File file2) throws FileNotFoundException, IOException
{
BufferedInputStream in1 = null;
BufferedInputStream in2 = null;
try {
in1 = new BufferedInputStream(new FileInputStream(file1));
in2 = new BufferedInputStream(new FileInputStream(file2));
// compare the files
boolean result = true;
int i1, i2;
do {
i1 = in1.read();
i2 = in2.read();
result = (i1 == i2);
}
while ( result && i1 != -1 && i2 != -1);
return result;
}
finally
{
// close the streams
try {
if ( in1 != null )
in1.close();
}
finally
{
if ( in2 != null )
in2.close();
}
}
}
|
Conclusion
The safest way to ensure resources are always released is to use
try-catch-finally statements and release the resources in the finally clause.
| |
| | | | | |