|
Now that you know what exceptions are and how they are handled, let us turn our focus on how the exceptions are thrown in the first place. When you know about certain erroneous conditions that may arise in your code, you can check for them. But how will you notify the users of your code about it? The following two sections discuss how you can write methods that throw exceptions to notify the users about the possible problematic conditions.
Throwing an exception consists of creating an exception object and handing it to the Java runtime system. Only objects of java.lang.Throwable class can be thrown either by JVM or by using the throw statement.
Exceptions are thrown with a throw statement. The use of this statement is simple. It takes an exception object as an argument. Say for instance, your code throws an NullPointerException as:
void method(String filename) {
if(filename == null) {
throw new NullPointerException(“File name is null”);
}
}
The exception object is created and thrown in one line. You can also create the exception object first and then throw it as:
void method(String filename) {
NullPointerException npExp =
new NullPointerException(“File name is null”);
if(filename == null){
throw npExp;
}
}
It is usually recommended however that the exception object should be created on the same line where it is thrown. There is a good reason for that. The exception gathers information about the point where it is created such as which method it is in, the declaring class and on what line number it is created (in the Java source file). That information is reported in the stack trace when the exception is thrown. Therefore, it is better to create the exception object right where it is thrown as that way it can give precise information about its circumstances.
|
|
The call stack trace of an exception shows the sequence of method calls that brought you to the point at which the exception was thrown. |
The throws clause in method declaration
We have seen earlier that the Java compiler ensures that any checked exceptions that may be thrown are properly caught. However, the method that throws an exception may not be the right candidate to handle it. In that case, the method should simply declare which exceptions it may throw, using the throws clause. This means that the method will not handle the exception but will pass it on to the calling method instead. In the listing 5.2, a method doSomeFileIO() uses the throws keyword to identify the list of possible exceptions it might throw.

In the above code, the method doSomeFileIO() is declaring that it might throw exceptions FileNotFoundException or EOFException. If does not mean that it will throw them each time it is executed, but it suggests the possibility that it might throw them. For instance, it will throw the FileNotFoundException exception if the file hello.txt is not found or it will throw the EOFException exception if the file hello.txt is found but its size is 0.
|
|
Earlier in exception handling, we saw how the class hierarchy plays an important role in the order of execution of the exception handlers. The class hierarchy is also important in the case of the throws clause of method declaration. For instance, the listing 5.2, the method throws FileNotFoundException and EOFException. Both these exceptions are the subclasses of an IOException. Therefore, the method can simply say that it throws an IOException instead of saying that it throws both these exceptions. |
When the doSomeFileIO() declares that it might throw two exceptions, the calling method of doSomeFileIO()knows it and needs to do something. It has two choices to deal with the exceptions that may be thrown by doSomeFileIO().
It can handle them with try-catch block.
It can declare (that it might) throw them with the throws clause (just the way the method doSomeFileIO() is declaring)
Table 5.8 illustrates the different ways the calling method of doSomeFileIO() deals with the exceptions thrown by doSomeFileIO().
Table 5.8 Legitimate ways of dealing with declared exceptions
|
|
The calling method of doSomeFileIO() |
|
Handling the declared exceptions with try-catch. |
void callerMethod() { try { doSomeFileIO(); }catch(FileNotFoundException fnfExp) { fnfExp.printStackTrace(); }catch (EOFException eofExp) { eofExp.printStackTrace(); } } |
|
Handling a generic exception for the declared exceptions with try-catch. |
void callerMethod() { try { doSomeFileIO(); }catch(IOException ioExp) { ioExp.printStackTrace(); } } |
|
Declaring the exceptions with throws clause |
void callerMethod() throws FileNotFoundException, EOFException { doSomeFileIO(); } |
|
Declaring (that the method might throw) the generic IOException |
void callerMethod() throws IOException { doSomeFileIO(); } |
Remember that if a method declares that it may throw an exception and if that exception is a checked exception, the compiler makes sure that the calling method either declares it or handles it. If not, then the code fails to compile. On the other hand, a method may declare to throw an unchecked exception, but the client methods need not deal with it. As such, the compiler does not enforce you to either declare or handle the unchecked exceptions.
|
|
The discretion on when a method throws an exception as oppose to handling it is very much based on the method’s responsibility. A well-known coding practice programming-by-contract suggests that the method should have a well-defined responsibility and should declare it in its contract with the other methods. For instance, a method sendMail() in a mail application has a responsibility to provide the send-mail functionality. It may only declare that IncorrectAddressException may occur. The calling methods that are aware of its contract will make sure to deal with that exception. |
Propagating uncaught exceptions
In the last section, we saw that methods can simply throw an exception if they choose not to handle it. The unchecked exception does not even need to be declared or handled at compile time. What would happen if no method handled them? Such exceptions are uncaught thought out the code. If they are actually thrown at runtime, the control propagates to see if any method handles them. If not, then the JVM handles them by printing the method-call stack trace. Now let us see what a stack trace is.
The call stack stores the sequence of method calls. For instance, if your applications main method calls method1() of a particular class which in turn calls method2(), then the call stack will have main(), method1() and method2() from bottom to top in that stack.
|
|
If you handle an exception in a catch block, you can print the stack trace with the printStackTrace() method of exception object to get some debugging hints. |
When an exception is thrown, it is due to some erroneous condition. As you can imagine, it certainly helps to get as much information about the erroneous condition as possible. You can use this information to take further action for correcting the problem. The stack trace is provided precisely for this reason. It is printed to give you some debugging hints as to why and where the exception has occurred.
In the case of uncaught exceptions, the JVM prints the stack trace to help you in debugging. For instance, in the following code, main(), method1() and method2() all declare that they may throw an exception of type IOException. None of them actually handles it with try-catch block.
public class PropagatingExceptionTest {
void method1() throws IOException {
// Statements
method2();
// Statements
}
void method2() throws IOException {
// Statements
throw new IOException();
// Statements
}
public static void main(String[] args) throws IOException {
new PropagatingExceptionTest().method1();
}
}
If IOException is indeed thrown in any of these methods at runtime, it will be uncaught. Figure 5.4 demonstrates how the uncaught IOException is propagated during a typical execution of the above program.

Figure 5.4 Propagation of uncaught exception.
|
|
The JVM calls the main() method of your application and the main() method’s entry is made on the top of method call stack. |
|||
|
|
The main() method calls method1() and method1’s entry is made on the top of method call stack.
|
|||
|
|
The method1() method calls method2() and method2’s entry is made on the top of method call stack.
|
|||
|
|
method2() throws an IOException during its execution. An exception object is created with a call stack trace. Since method2() does not handle IOException, the exception object is propagated back to the calling method, method1(). |
|||
|
|
method1() does not handle IOException either. So, the exception object is propagated back to the calling method of method1(), the main() method. |
|||
|
|
main() does not handle IOException. It, too, declares that it may throw the IOException. So the exception object is propagated back to the JVM. |
|||
|
|
The JVM halts and prints the stack trace as an output. |
In our example, the IOException is a checked exception. Therefore, it needs to be declared or handled in the methods where it may be thrown. On the other hand, any unchecked exception need not be declared or handled. But if it is thrown at runtime and is uncaught, it propagates in the same fashion as the checked exception.