It’s important that we always ask, “If an exception occurs, will everything be properly cleaned up?” Most of the time we’re fairly safe, but with 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 we throw an exception from inside a constructor, these cleanup behaviors might not occur properly. This means we must be especially vigilant when writing a constructor. We might think finally is the solution.
// exceptions/CleanupIdiom.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Disposable objects must be followed by a try-finally
class NeedsCleanup { // Construction can't fail
private static long counter = 1;
private final long id = counter++;
public void dispose() {
System.out.println("NeedsCleanup " + id + " disposed");
}
}
class ConstructionException extends Exception {}
class NeedsCleanup2 extends NeedsCleanup {
// Construction can fail:
NeedsCleanup2() throws ConstructionException {}
}
public class CleanupIdiom {
public static void main(String[] args) {
// [1]:
NeedsCleanup nc1 = new NeedsCleanup();
try {
// ...
} finally {
nc1.dispose();
}
// [2]:
// If construction cannot fail,
// you can group objects:
NeedsCleanup nc2 = new NeedsCleanup();
NeedsCleanup nc3 = new NeedsCleanup();
try {
// ...
} finally {
nc3.dispose(); // Reverse order of construction
nc2.dispose();
}
// [3]:
// If construction can fail you must guard each one:
try {
NeedsCleanup2 nc4 = new NeedsCleanup2();
try {
NeedsCleanup2 nc5 = new NeedsCleanup2();
try {
// ...
} finally {
nc5.dispose();
}
} catch (ConstructionException e) { // nc5 const.
System.out.println(e);
} finally {
nc4.dispose();
}
} catch (ConstructionException e) { // nc4 const.
System.out.println(e);
}
// System.out.println(NeedsCleanup.counter); // compile error: counter has private access in
// NeedsCleanup
}
}
/* Output:
NeedsCleanup 1 disposed
NeedsCleanup 3 disposed
NeedsCleanup 2 disposed
NeedsCleanup 5 disposed
NeedsCleanup 4 disposed
*/
The messiness of exception handling here is a strong argument for creating constructors that cannot fail, although this is not always possible. Note that if dispose() can throw an exception you might need additional try blocks. Basically, we must think carefully about all the possibilities and guard for each one.
try-with-resources
solution:
// exceptions/InputFile2.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import java.io.*;
import java.nio.file.*;
import java.util.stream.*;
public class InputFile2 {
private String fname;
public InputFile2(String fname) {
this.fname = fname;
}
public Stream<String> getLines() throws IOException {
return Files.lines(Paths.get(fname));
}
public static void main(String[] args) throws IOException {
new InputFile2("InputFile2.java").getLines().skip(16).limit(1).forEach(System.out::println);
}
}
/* My Output:
public Stream<String> getLines() throws IOException {
*/
references:
1. On Java 8 - Bruce Eckel
2. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/exceptions/CleanupIdiom.java
3. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/exceptions/InputFile2.java
5. https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#lines-java.nio.file.Path-