Friday, June 24, 2011

Remember to properly close the File!

Today few comments on how to deal with Files, Streams, Connections and anything else that is ‘closeable’ in Java. To simplify we will focus on a FileWriter class and a simple code snippet with a bug that uses it:

public void writeToFile(String fileName, String content) {
    try {
      FileWriter file = new FileWriter(fileName);
      file.write(content);
    } catch (IOException e) {
      // Log the exception
    }
}

So what’s wrong? Well, plenty of things: we do not check the input values (maybe they are null or rubbish?), we do not care whether the file existed or is a directory, we do not care about specifying the encoding for the file. On the other hand you could argue that all of this are not problems but ‘features’, that the contract of this method says: just write using a FileWriter. That may be, but still there is one thing wrong with this code – we did not close the file.

Is that important? Well, yeah! Not closing the file may result in corrupted output of your write and makes the system still occupy the resources that could be freed. Always remember to close your Files, Streams, Connections! Let’s make a quick fix to the code and remove the bug:

public void writeToFile(String fileName, String content) {
    try {
      FileWriter file = new FileWriter(fileName);
      file.write(content);
      file.close();
    } catch (IOException e) {
      // Log the exception
    }
}

Fixed? I guess some of you think it is fixed, but in fact it is not – what if there is an exception thrown during the write? The method will exit and the file will not be closed! Okay, there is a fix for that – use try/finally clause. This will guarantee that no matter what will happen in write the file will be closed.

public void writeToFile(String fileName, String content) {
    FileWriter file = null;
    try {
      file = new FileWriter(fileName);
      file.write(content);
    } catch (IOException e) {
      // Log the exception
    } finally {
      file.close();
    }
}

Fixed? Well… no. The exception can be also thrown in the FileWriter constructor, so in finally block the file will be null. And this will result in NullPointerException. Let’s deal with that:

public void writeToFile(String fileName, String content) {
    FileWriter file = null;
    try {
      file = new FileWriter(fileName);
      file.write(content);
    } catch (IOException e) {
      // Log the exception
    } finally {
      if (file != null) {
        file.close();
      }
    }
}

Done? Not yet… file.close() also can throw an exception! Since the method writeToFile does not allow any checked exception to be thrown, we have to deal with that also:

public void writeToFile(String fileName, String content) {
    try {
      FileWriter file = new FileWriter(fileName);
      try {
        file.write(content);
      } finally {
        file.close();
      }
    } catch (IOException e) {
      // Log the exception
    }
}

Finally (pun intended) we are done and the bug is fixed! Notice that in this code we did not had to check whether file is null before closing it – this is because the only way file is null is if an exception was thrown in the constructor in line 3 and this would lead to skipping most of the code and going to line 10. As you can see closing a closeable object is not a trivial task, so remember to do it properly!

There is another issue appearing in this problem – readability. If this the code above seems readable to you, then try to imagine dealing with more than one closeable object – it can get really messy! Therefore whenever you work with closeable objects it is a good idea to split the code into 2 methods: one that deals with exceptions and closing and the other which just does the main code. To see how this would work with the code above take a look at the following:

// Here we handle opening the file and manage exceptions.
public void writeToFile(String fileName, String content) {
    try {
      FileWriter file = new FileWriter(fileName);
      try {
        performWriteToFile(file, content);
      } finally {
        file.close();
      }
    } catch (IOException e) {
      // Log the exception
    }
}

// Here we perform all the operations on the file.
// This method usually is much longer.
private void performWriteToFile(FileWriter file,
        String content) throws IOException {
    file.write(content);
}


No comments:

Post a Comment

Chitika