finally
The
finallykeyword is used to define a block of code that will be executed after atry-catchblock, regardless of whether an exception occurred or was caught. It is useful for cleaning up resources, like closing files or releasing locks, that need to be done regardless of the outcome of thetry-catchblock.
In the previous lesson, we ended with the following getTotalMilesRun method. It's pretty good, but it has one problem: if something goes wrong while reading the file, we'll skip reading the rest of the file and throw an Exception, and we would skip closing the Scanner.
We can ensure that we always hit the fileScanner.close() line by enclosing it in a finally block.
This requires relatively minor changes to our method.
I've marked the new parts with comments in the code below.
double getTotalMilesRun(String fileName) {
if (fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("fileName cannot be null or empty");
}
// NEW: We need to declare the Scanner in a broader scope
// so it's accessible in the finally block.
Scanner fileScanner = null;
double totalMiles = 0.0;
try {
fileScanner = new Scanner(new File(fileName));
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
String[] parts = line.split(",");
if (parts.length < 2) {
System.out.println("Invalid line: " + line);
continue;
}
String name = parts[0];
double miles;
try {
miles = Double.parseDouble(parts[1]);
} catch (NumberFormatException nfe) {
System.out.println("Invalid miles value: " + parts[1]);
continue;
}
totalMiles += miles;
}
} catch (FileNotFoundException fnfe) {
System.out.println("Could not find a file called " + fileName);
System.out.println("Error message: " + fnfe.getMessage());
} finally {
// This will be executed no matter what
if (fileScanner != null) {
fileScanner.close();
}
}
return totalMiles;
}
All roads lead to finally
Let's update our mental mode of the flow of control with try-catch blocks by adding a finally block.
flowchart TD A[try block] A -->|No exception or return| C[finally block] A -->|Exception, caught| D[catch block] A -->|Return statement<br>or uncaught Exception| C D --> C C -->|No pending exception<br/>or return| E[Continue with rest of code] C -->|Pending return value<br>or exception| F[Return value/Exception<br/>propagates up call stack]
Notice how we reach the finally block no matter what.
Every path through the try-catch block goes through finally.
- If the
tryblock goes through without a hitch, we'll go throughfinallybefore continuing with the code after thetry-catch-finallyblock. - If the
tryblock encounters an exception,- If that exception is handled in a
catchblock, we'll go through thecatchblock and then through thefinallyblock, before continuing with the code after thetry-catch-finallyblock. - If that exceptoin is not handled by any
catchblock, we'll still go throughfinallybefore throwing the Exception up the call stack to the method that called us
- If that exception is handled in a
- If a
tryorcatchblockreturna value, we'll still go throughfinallybefore exiting the method.
try-with-resources
The pattern we're seeing above—declaring a resource (like a Scanner) outside the try block, initializing it inside try, and then closing it in finally—is a common idiom in Java.
Java therefore introduced a newer syntax called "try-with-resources" that takes care of closing your resources for you.
You should always use the try-with-resources syntax when working with resources that need to be closed, like
Scanner,FileReader,BufferedReader,InputStream,OutputStream, etc.
We can therefore re-write our method as follows (and I promise this is its final form):
double getTotalMilesRun(String fileName) {
if (fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("fileName cannot be null or empty");
}
double totalMiles = 0.0;
// NEW: Use try-with-resources to automatically close the Scanner
// The fileScanner variable is only visible inside the try block
try (Scanner fileScanner = new Scanner(new File(fileName))) {
while (fileScanner.hasNext()) {
String line = fileScanner.nextLine();
String[] parts = line.split(",");
if (parts.length < 2) {
System.out.println("Invalid line: " + line);
continue;
}
String name = parts[0];
double miles;
try {
miles = Double.parseDouble(parts[1]);
} catch (NumberFormatException nfe) {
System.out.println("Invalid miles value: " + parts[1]);
continue;
}
totalMiles += miles;
}
} catch (FileNotFoundException fnfe) {
System.out.println("Could not find a file called " + fileName);
System.out.println("Error message: " + fnfe.getMessage());
}
return totalMiles;
}