Constructors

Bruce Eckel’s Thinking in Java Contents | Prev | Next

When
writing code with exceptions, it’s particularly important that you always
ask, “If an exception occurs, will this be properly cleaned up?”
Most of the time you’re fairly safe, but in constructors there’s a
problem. The constructor puts the object into a safe starting state, but it
might perform some operation – such as opening a file – that
doesn’t get cleaned up until the user is finished with the object and
calls a special cleanup method. If you throw an exception from inside a
constructor, these cleanup behaviors might not occur properly. This means that
you must be especially diligent while you write your constructor.

//: Cleanup.java
// Paying attention to exceptions
// in constructors
import java.io.*;
 
class InputFile {
  private BufferedReader in;
  InputFile(String fname) throws Exception {
    try {
      in =
        new BufferedReader(
          new FileReader(fname));
      // Other code that might throw exceptions
    } catch(FileNotFoundException e) {
      System.out.println(
        "Could not open " + fname);
      // Wasn't open, so don't close it
      throw e;
    } catch(Exception e) {
      // All other exceptions must close it
      try {
        in.close();
      } catch(IOException e2) {
        System.out.println(
          "in.close() unsuccessful");
      }
      throw e;
    } finally {
      // Don't close it here!!!
    }
  }
  String getLine() {
    String s;
    try {
      s = in.readLine();
    } catch(IOException e) {
      System.out.println(
        "readLine() unsuccessful");
      s = "failed";
    }
    return s;
  }
  void cleanup() {
    try {
      in.close();
    } catch(IOException e2) {
      System.out.println(
        "in.close() unsuccessful");
    }
  }
}
 
public class Cleanup {
  public static void main(String[] args) {
    try {
      InputFile in =
        new InputFile("Cleanup.java");
      String s;
      int i = 1;
      while((s = in.getLine()) != null)
        System.out.println(""+ i++ + ": " + s);
      in.cleanup();
    } catch(Exception e) {
      System.out.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace();
    }
  }
} ///:~ 

This
example uses Java 1.1
IO classes.

The
constructor for
InputFile
takes a
String
argument, which is the name of the file you want to open. Inside a
try
block, it creates a
FileReader
using the file name. A
FileReader
isn’t particularly useful until you turn around and use it to create a
BufferedReader
that you can actually talk to – notice that one of the benefits of
InputFile
is that it combines these two actions.

If
the
FileReader
constructor is unsuccessful, it throws a
FileNotFoundException,
which must be caught separately because that’s the one case in which you
don’t want to close the file since it wasn’t successfully opened.
Any
other
catch clauses must close the file because it
was
opened by the time those catch clauses are entered. (Of course, this is
trickier if more than one method can throw a
FileNotFoundException.
In that case, you might want to break things into several
try
blocks.)
The
close( )
method throws an exception that is tried and caught even though it’s
within the block of another
catch
clause – it’s just another pair of curly braces to the Java
compiler. After performing local operations, the exception is re-thrown, which
is appropriate because this constructor failed, and you wouldn’t want the
calling method to assume that the object had been properly created and was valid.

In
this example, which doesn’t use the aforementioned flagging technique, the
finally
clause is definitely
not
the place to
close( )
the file, since that would close it every time the constructor completed. Since
we want the file to be open for the useful lifetime of the
InputFile
object this would not be appropriate.

String getLine() throws IOException {
  return in.readLine();
}

But
of course, the caller is now responsible for handling any
IOException
that might arise.

The
cleanup( )
method must be called by the user when they are finished using the
InputFile
object to release the system resources (such as file handles) that are used by
the
BufferedReader
and/or
FileReader
objects.
[46]
You don’t want to do this until you’re finished with the
InputFile
object, at the point you’re going to let it go. You might think of
putting such functionality into a finalize( )
method, but as mentioned in Chapter 4 you can’t always be sure that
finalize( )
will be called (even if you
can
be sure that it will be called, you don’t know
when).
This is one of the downsides to Java – all cleanup other than memory
cleanup doesn’t happen automatically, so you must inform the client
programmer that they are responsible, and possibly guarantee that cleanup
occurs using
finalize( ).

In
Cleanup.java
an
InputFile
is created to open the same source file that creates the program, and this file
is read in a line at a time, and line numbers are added. All exceptions are
caught generically in
main( ),
although you could choose greater granularity.


[46]
In C++, a
destructor
would handle this for you.

More by Author

Must Read