Things go wrong.
In Java, when things go wrong enough, an Exception is thrown. You can catch Exceptions, and you can also throw them yourself.
Exception (a class which is itself a subclass of Throwable) is the superclass of a large collection of subclasses that describe specific occurrences when your program runs. For example, ArithmeticException is thrown for conditions like dividing by zero, and ClassCastException occurs when an attempt is made to cast an object to an incompatible class.
Different subclasses of Exception can have fields and methods unique to themselves. For example, SQLException has a method for returning an error code from the database management system.
And if necessary, you can create your own subclasses of Exception to signal and provide data for exception conditions unique to your project.
Constructors for Class: Exception
Exception has three constructors:
Constructor | Function |
Exception() | Creates an Exception with a null message. |
Exception(String message) | Creates an Exception with and sets its message to the value provided. |
Exception(String message, Throwable cause) | Creates an Exception with the message provided and cause as the root cause (i.e., this Exception is being created because cause occurred). |
Methods Common to All Exceptions
All of Exception’s methods are inherited from Throwable.
Method | Function |
getCause() | Returns a Throwable which is the root cause of this Exception, if any. The returned Throwable may have a root cause of its own. |
getMessage() | Returns a String which is the message associated with the Exception. |
printStackTrace() | Displays the stack trace on the standard system output console. (There are overloads that send the stack trace elsewhere.) |
getLocalizedMessage() | Returns a localized version of getMessage(). If localized message has not been set, the result is the same as getMessage(). |
setLocalizedMessage(String msg) | Sets the value to be returned by getLocalizedMessage(). The localized message is usually only used when the audience is in different locales. For example, you might set the “localized” message to the original message translated to a foreign language. |
Checked and Unchecked Exceptions
Exceptions fall into two categories: checked and unchecked. What’s the difference? Practically speaking, not much. A checked Exception is one that is explicitly thrown by a method (or one that it calls) by using a throw statement, and an unchecked Exception is thrown without the use of a throw statement.
Java requires you to declare in a method header the types of checked Exceptions (or their superclass Exceptions) it throws, like this:
public myMethod() throws SomeException {...}
For example, if you make a call to a database method–say, commit()–in package java.sql, the IDE will confront you with a notice about the checked Exception SQLException and ask you to respond. That’s because the method header for commit() looks something like this:
public void commit() throws SQLException {...}
You have two choices here. One is to catch the Exception and handle it. The second is to pass the buck to whoever called your method by adding the clause: throws SQLException
or throws Exception
to your method declaration–in which case it becomes the problem of the method that calls yours. (In the method declaration, you can specify as many checked Exceptions as you need to, separated by commas. It suffices to name superclasses of the Exceptions that are actually thrown.)
This code snippet illustrates two checked Exceptions, one thrown by the method itself and one thrown by a method it calls. The single superclass Exception could have replaced both.
private void myMethod(int dividend)
throws SQLException, MyZerodivideException {
myDatabaseConnection.commit();
if (int == 0) {
throw new MyZerodivideException
("Who said you could do that?");
}
Exception Handling
As mentioned above, when an Exception is thrown, the developer has a choice: handle it; or let it work its way up the call stack until some calling method, or the System, handles it. (And the System’s way of handling an Exception is to call printStackTrace() and shut down.)
Handling an Exception is done in try/catch/finally blocks that look like this:
try {
<do something here>
} catch (SomeException e) {
<react to SomeException>
} catch (SomeOtherException e) {
<react to SomeOtherException>
} catch (Exception e) {
<react to any other kind of Exception>
} finally {
<do something unconditionally>
}
What’s happening here?
- The statements in the try block is some code in which you anticipate some kind of Exception might occur.
- The various catch blocks are where you handle different kinds of Exceptions. You can have one catch for each kind of Exception you anticipate, and when that Exception occurs, the associated catch block is executed. You don’t have to catch every kind of checked Exception that might occur (in which case you put them in the throws clause in the method header instead).
If you don’t need to handle the Exceptions, you don’t have to have any catches. But if you need to do any Exception handling at all, it makes sense to have one catch that handles Exception for the unchecked Exceptions as well as any checked Exceptions the preceding catch blocks don’t.
The order of the catch clauses is significant! No catch can specify a subclass of the Exception type of a previous catch, because the previous catch includes the subclass. For example, you can’t follow a catch on IOException with a catch for SQLException, because every SQLException is also an IOException. - The finally block is executed whether the try block encounters an Exception or not; if an Exception is caught, the finally block executes after any catch block. One example of this is where a program closes a database connection whether or not the database operation succeeds.
Last, if some method that called the one in which the Exception occurs needs to process the Exception as well–and this includes letting the System take over and stop everything if desired–you can throw the Exception, or even create an entirely new Exception and throw that. For example:
try {
String result = database.get("KEY");
} catch (SQLException e) {
throw new Exception("Something has gone wrong", e);
} finally {
database.close();
}
Notice how the new Exception has the SQLException as its cause!
How To Handle An Exception?
Back in the olden (i.e., COBOL) times, structured programming was the thing. The idea was your program is supposed to accomplish some task, like producing payroll checks. This task could be divided into subtasks:
- get the information for the next employee;
- find out how long the employee worked during the pay period;
- calculate pay;
- print a check or send a direct deposit.
Each of these might have other subtasks still: calculate pay might be divided into:
- calculate gross pay
- calculate taxes
- calculate deductions
And calculate taxes is further subdivided into:
- calculate Federal income tax
- calculate FICA tax
- calculate Medicare tax
- calculate state income tax
- calculate local income tax
Java is no different. You’d have a method called generatePayroll(), which calls processEmployee() which calls calculatePay() which calls calculateTaxes() which calls calculateFederalIncomeTax(). This chain of method calls is known as the call stack.
The processing of one employee is what we call one unit of work, and (to borrow a term from structured programming) we give the name top level to the method that calls all the other methods involved in the unit of work. If an Exception occurs during the processing of one unit of work, we’ll want to handle that–probably by writing a notification to a log and rolling back any database changes.
Now, suppose something goes wrong in calculateFederalIncomeTax() and an Exception is thrown. One of two things might happen. First is that the method handles the Exception and doesn’t inform any of the methods that called it (i.e., it consumes the Exception):
public float calculateFederalIncomeTax(float wage, Employee who) {
try {
<do something>
} catch (Exception e) {
System.out.println("I couldn't calculate!");
}
The other is that method either handles the Exception or not, as appropriate–but then throws the Exception (or a new Exception) up the call stack until some method, or the System, catches it. If calculateFederalIncomeTax() has an Exception and doesn’t catch it, then the Exception gets thrown to calculateTaxes(); if it isn’t caught there, it’s thrown to calculatePay(); if not caught there, it’s thrown to processEmployee(), and then generatePayroll(); and if not caught by whatever called generatePayroll(), it’s caught by the System.
Often you’ll see advice that you should always handle Exceptions where they first occur. Ignore that advice! Ten times out of ten, there’s nothing worth doing where the Exception first occurs, and you certainly don’t want to consume it!
It’s better to let the Exception (or some replacement Exception) work its way up the call stack to, in our example, processEmployee() or generatePayroll() (depending on how the code is written), which will realize that the employee processing failed. The nature of the failure is usually irrelevant; the catch block will react the same way for any failure, probably by logging it and doing some cleanup.
public float calculateFederalIncomeTax(float wage, Employee who)
throws Exception {
try {
<do something>
} catch (Exception e) {
System.out.println("I couldn't calculate!");
throw new Exception("Unable to calculate Federal income tax for employee " + who.getName(),
e);
}
Usually this means neither calculateFederalIncomeTax() nor any method between it and processEmployee() (or generatePayroll()), does anything at all to handle the Exception. If the Exception gets consumed before that, the top-level method would be oblivious to the fact that it didn’t succeed.
And that would be a terrible thing.
What You Need To Know
- An Exception is thrown when a problem occurs.
- Exceptions are either checked (explicitly thrown) or unchecked. The advantage of checked Exceptions is that the IDE will insist they be addressed by the developer who calls a method having one.
- Otherwise, there is no difference between the two. Always code top-level methods as though an Exception might occur under them and be prepared to recover from it.
- Exceptions are caught by try/catch/finally blocks.
- It’s almost always wiser to avoid consuming an Exception except at the top level of a unit of work. If any lower level also needs to handle the Exception, it’s almost always preferable to rethrow the Exception (or a replacement) so the top-level method can handle it too.
- You can create your own subclasses of Exception to signal and provide data for exception conditions unique to your project.
Lesson 9: Communicating with the outside