I have been developing a Java EE Web application recently and I have used combination of java.io.InputStream and java.io.OutputStream classes. I had some problems with its close() method, so I did a little research and I found out some interesting things. Let's check them out.
There are also two family classes for working with characters - java.io.Reader and java.io.Writer. All this classes implement one common method (already mentioned) called public void close() throws IOException, which is real piece of art and nightmare, because it must be called to free resources, but it also throws checked IOException, so it requires additional handling. So if you do everything by book this masterpiece would come out:
public static void main(String[] args)
{
String fileLocation = "/docs/README.txt";
FileInputStream fis = null;
try
{
fis = new FileInputStream(fileLocation);
int read = 0;
byte[] buffer = new byte[8192];
while((read = fis.read(buffer)) > -1)
{
System.out.print(new String(buffer, 0, read));
}
}
catch(IOException e)
{
Logger.getGlobal().log(Level.SEVERE, "Failed to read file at: " + fileLocation, e);
}
finally
{
try
{
if (fis != null)
{
fis.close();
}
}
catch(IOException e)
{
Logger.getGlobal().log(Level.SEVERE, "Failed to close stream reading file at: " + fileLocation, e);
}
}
}
I think it is quite obvious why I call it masterpiece - there is more error handling code than logic. Furthermore imagine the amount of code, if there would be an
OutputStream object.
The same problem is with
java.sql classes, such as
java.sql.Connection,
java.sql.PreparedStatement,… So what can we do? Well as research shows, it depends on the version of Java you are running.
JavaSe 7
JavaSe7 introduced
java.lang.AutoClosable interface which is implemented by all streaming
java.io classes and
java.sql classes. The documentation states:
Closes this resource, relinquishing any underlying resources. This method is invoked automatically on objects managed by the try-with-resources statement. Nice so the correct code then is:
public static void main(String[] args)
{
String fileLocation = "/docs/README.txt";
try(FileInputStream fis = new FileInputStream(fileLocation);)
{
int read = 0;
byte[] buffer = new byte[8192];
while((read = fis.read(buffer)) > -1)
{
System.out.print(new String(buffer, 0, read));
}
}
catch(IOException e)
{
Logger.getGlobal().log(Level.SEVERE, "Failed to read file at: " + fileLocation, e);
}
}
Fancy isn't it? Yes it is, but
WARNING: if you use this JavaSe7 feature set your project's
Source/binary format in IDE to JDK7. Without this, this code may be run on lower versions of Java and wonderful bug is created. Again note, you can use this feature for
java.sql classes too.
Update: on 22.06.2014 I noticed a small but important mistake in the example above: a FileInputStream instance (
fis) was not inside brackets at
try keyword. If you want to use try-with-resources then all AutoClosable instances must be inside brackets at
try keyword, as is now correctly displayed in the example above.
JavaSe 5 and JavaSe6
Java 5 and 6
java.io streaming classes implement j
ava.io.Closeable interface. There are two solutions available: an
Apache Commons IO library or implementation of custom solution.
The Apache Commons IO library has
IOUtils class which has numerous
closeQuietly methods
implemented - implementation of them is quite simple:
public static void closeQuietly(final OutputStream output)
{
closeQuietly((Closeable)output);
}
public static void closeQuietly(final Closeable closeable)
{
try
{
if (closeable != null)
{
closeable.close();
}
}
catch (final IOException joe)
{
// ignore
}
}
}
The Commons IO library is really a handy tool, but there is something to be considered - the catch statement is empty - this bad practice is also known as exception swallowing - well in the presented scenario there is really not much that can be done, but I would at least attach a Logger on the finest/trace level.
I have used many APIs which take InputStream and produce something in the OutputStream. In some cases there was also a need to redirect OutputStream to InputStream via PipedInputStream PipedOutputStream combo - so four closeQuietly calls - no thank you. This is a feature I miss in Apache's library. So let's try to make something for us, lazy developers.
// Package
package ioutils;
// Imported classes
import java.io.Closeable;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* An utility class for working with java.io
* @author Leon Dobnik
*/
public abstract class IoUtils
{
/**
* Closes any java.io.* object which implements java.io.Closeable interface
* @param closeable closeable object to close
* @see java.io.Closeable
*/
public static void closeQuietly(final Closeable closeable)
{
if (closeable != null)
{
try
{
closeable.close();
}
catch(IOException e)
{
Logger.global.log(Level.FINEST, "Failed to close Closeable " + closeable.getClass().getCanonicalName(), e);
}
}
}
/**
* Closes multiple java.io.* objects that implement java.io.Closeable interface
* @param closeables to close
* @see java.io.Closeable
*/
public static void closeQuietly(Closeable... closeables)
{
for (Closeable closeable : closeables)
{
closeQuietly(closeable);
}
}
}
public static void main(String[] args)
{
String fileLocation = "C:\\source.file";
String fileDestination = "C:\\destination.file";
FileInputStream fis = null;
FileOutputStream fos = null;
try
{
fis = new FileInputStream(fileLocation);
fos = new FileOutputStream(fileDestination);
int read = 0;
byte[] buffer = new byte[8192];
while((read = fis.read(buffer)) > -1)
{
fos.write(buffer, 0, read);
}
fos.flush();
}
catch(IOException e)
{
Logger.global.log(Level.SEVERE, "Failed to read file at: " + fileLocation, e);
}
IoUtils.closeQuietly(fis, fos); // Nice and shiny close :)
}
So to make your own
IoUtils class you need to implement just one method -
closeQuietly(Closeable closeable). If you want to pass an unlimited number of parameters in single call, add an overloaded method with
Cloneable… cloneable parameter. The demonstration function copies one file into another (Note: I know that there are
Buffered* Stream classes, but that's not the point of this post).
Unfortunately the
Closeable interface in available only for
java.io classes, so for
java.sql classes you need to write custom
closeQuietly methods for each class (
java.io.Connection,
java.io.Statement, …)
Java Se 1.4
Well if you still use this ancient relic of the past, ready yourself for the implementation marathon - there is no Closeable interface - so for every class that is to be closed quietly a method called closeQuietly must be implemented (of course you can name it fancyClose or helloKittyClose if you want) - one for InputStream, one for OutputStream and so on.
There is also no ClassType… parameterName feature - but that does not mean that this ancient relic cannot handle features of JavaSe5 and JavaSe6. It can, but solution and use is not so elegant.
// Package
package ioutils;
// Imported classes
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.logging.Logger;
/**
* An utility class for working with java.io
* @author Leon Dobnik
*/
public abstract class IoUtils
{
/**
* Closes java.io.InputStream without throwing exception
* @param is stream to close
* @see java.io.InputStream
*/
public static void closeQuietly(final InputStream is)
{
internalCloseQuietly((Object)is);
}
/**
* Closes java.io.OutputStream without throwing exception
* @param os stream to close
*/
public static void closeQuietly(final OutputStream os)
{
internalCloseQuietly((Object)os);
}
/**
* Invokes close() method on some java object
* @param o instance of which close() method may be invoked
*/
private static void internalCloseQuietly(final Object o)
{
if (o != null)
{
try
{
Method closeMethod = o.getClass().getMethod("close", null);
if (closeMethod != null)
{
closeMethod.invoke(o, null);
}
}
catch(Exception e) // In real world, catch specific exceptions...
{
Logger.global.finest("Failed to call close() method on: " + o.getClass().getName());
}
}
}
/**
* Closes quietly multiple objects that implement close() method. If object does
* not have close() method it will be ignored.
* @param objects
*/
public static void closeQuielty(final Object[] objects)
{
if (objects != null)
{
for (int i = 0; i < objects.length; i++)
{
internalCloseQuietly(objects[i]);
}
}
}
}
public static void main(String[] args)
{
String fileLocation = "C:\\fileSource.txt";
String fileDestination = "C:\\fileDestination.txt";
FileInputStream fis = null;
FileOutputStream fos = null;
try
{
fis = new FileInputStream(fileLocation);
fos = new FileOutputStream(fileDestination);
int read = 0;
byte[] buffer = new byte[8192];
while((read = fis.read(buffer)) > -1)
{
fos.write(buffer, 0, read);
}
fos.flush();
}
catch(IOException e)
{
Logger.global.log(Level.SEVERE, "Failed to read file at: " + fileLocation, e);
}
IoUtils.closeQuielty(new Object[]{fis, fos}); // Nice and shiny close :)
}
So in case of this relic a solution is to use Reflection API for invoking close() method on objects and writing public wrapper methods for supported types. The method closeQuietly(final Object[] objects) is an option if you want to close multiple objects in one call, but it is your decision if it meets coding standards of your organisation.
Any comments are welcome.