Throwing and Catching Exceptions
Navigate Exceptions topic: ) |
Language compilers are adept at pointing out most of the erroneous code in a program, however there are some errors that only become apparent when the program is executed. Consider the code in Listing 1.1; here, the program defines a method divide that does a simple division operation taking two integers as parameter arguments and returning the result of their division. It can safely be assumed that when the divide(4, 2) statement is called, it would return the number 2. However, consider the next statement, where the program relies upon the provided command line arguments to generate a division operation. What if the user provides the number zero (0) as the second argument? We all know that division by zero is impossible, but the compiler couldn't possibly have anticipated the user providing zero as an argument.
Listing 1.1: A simple division operation.
-
public class ExceptionTutorial01
-
{
-
public static int divide(int a, int b)
-
{
-
return a / b;
-
}
-
public static void main(String[] args)
-
{
-
System.out.println( divide(4, 2) );
-
if(args.length > 1)
-
{
-
int arg0 = Integer.parseInt(args[0]);
-
int arg1 = Integer.parseInt(args[1]);
-
System.out.println( divide(arg0, arg1) );
-
}
-
}
-
}
public class ExceptionTutorial01 { public static int divide(int a, int b) { return a / b; } public static void main(String[] args) { System.out.println( divide(4, 2) ); if(args.length > 1) { int arg0 = Integer.parseInt(args[0]); int arg1 = Integer.parseInt(args[1]); System.out.println( divide(arg0, arg1) ); } } }
Output for: java ExceptionTutorial01 1 0
2 Exception in thread "main" java.lang.ArithmeticException: / by zero at ExceptionTutorial01.divide(ExceptionTutorial01.java:5) at ExceptionTutorial01.main(ExceptionTutorial01.java:14)
Such exceptional code that results in erroneous interpretations at program runtime usually results in errors that are called exceptions in Java. When the Java interpreter encounters an exceptional code, it halts execution and displays information about the error that occurs. This information is known as a stack trace. The stack trace in the above example tells us more about the error, such as the thread - "main"
- where the exception occurred, the type of exception - java.lang.ArithmeticException
, a comprehensible display message - / by zero
, and the exact methods and the line numbers where the exception may have occurred.
Exception arguments [edit]
In the code above, the exception object for java.lang.ArithmeticException
was generated by the Java interpreter itself. However, there are times when you would need to explicitly create your own exceptions. As with any object in Java, you always create exceptions on the heap using new, which allocates storage and calls a constructor. There are two constructors in all standard exceptions:
- The default constructor; and,
- A constructor taking a string argument so that you can place pertinent information in the exception.
Listing 1.2: Instance of an exception object with the default constructor.
new Exception();
Listing 1.3: Instance of an Exception
object by passing string in constructor.
new Exception("Something unexpected happened");
This string can later be extracted using various methods, as you'll see.
The keyword throw produces a number of interesting results. After creating an exception object with new, you give the resulting reference to throw. The object is, in effect, "returned" from the method, even though that object type isn't normally what the method is designed to return. A simplistic way to think about exception handling is as a different kind of return mechanism, although you get into trouble if you take that analogy too far. You can also exit from ordinary scopes by throwing an exception. In either case, an exception object is returned, and the method or scope exits.
Listing 1.4: Throwing an exception results in an unexpected return from the method.
throw new Exception();
Anything after the throw statement would not be executed, unless the thrown exception is handled properly. Any similarity to an ordinary return from a method ends here, because where you return is some place completely different from where you return for a normal method call, i.e., you end up in an appropriate exception handler that might be far away - many levels on the call stack - from where the exception was thrown.
In addition, you can throw any type of Throwable, which is the exception root class. Typically, you'll throw a different class of exception for each different type of error. The information about the error is represented both inside the exception object and implicitly in the name of the exception class, so someone in the bigger context can figure out what to do with your exception. Often, the only information is the type of exception, and nothing meaningful is stored within the exception object.
Catching an exception [edit]
To see how an exception is caught, you must first understand the concept of a guarded region. This is a section of code that might produce exceptions and is followed by the code to handle those exceptions.
The try
block [edit]
If you are inside a method and you throw an exception (or another method that you call within this method throws an exception), that method will exit in the process of throwing. If you don't want a throw to exit the method, you can set up a special block within that method to capture the exception. This is called the try block because you "try" your various method calls there. The try block is an ordinary scope preceded by the keyword try.
Listing 1.5: A basic try block.
try { // Code that might generate exceptions }
If you were checking for errors carefully in a programming language that didn't support exception handling, you'd have to surround every method call with setup and error-testing code, even if you call the same method several times. With exception handling, you put everything in a try block and capture all the exceptions in one place. This means your code is much easier to write and read because the goal of the code is not confused with the error checking.
Exception handlers [edit]
Of course, the thrown exception must end up some place. This "place" is the exception handler, and there's one for every exception type you want to catch. Exception handlers immediately follow the try block and are denoted by the keyword catch:
Listing 1.6: Exception handling with catch blocks.
try { // Suppose the code here throws two exceptions: // NullPointerException and NumberFormatException, // then each is handled in a separate catch block. } catch(NullPointerException ex) { // Exception handling code for the NullPointerException } catch(NumberFormatException ex) { // Exception handling code for the NumberFormatException } // etc...
Using the syntax in Listing 1.6, we can write the potentially problematic division code in Listing 1.1 as under.
Listing 1.7: Catching 'division by zero' errors.
int result = 0; try { result = a / b; } catch(ArithmeticException ex) { result = 0; } return result;
Using the code in Listing 1.7, the program would now not abruptly terminate but continue its execution whilst returning zero for a division by zero operation. I know it's not the correct answer, but hey, it saved your program from terminating abnormally.
Exception classes in the JCL [edit]
The box 1.1 below talks about the various exception classes within the java.lang
package of the JCL.
Box 1.1: The Java exception classes
|
Figure 1.1: The exception classes and their inheritance model in the JCL. |
Catch clauses [edit]
There are three distinct types of catch clauses used by Java programmers. We have already explored the first rule in the code listings above. Given below are all three clauses; we will explain all three in greater detail in the next sections.
- The catch-list clause
- The catch-any clause
- The multi-catch clause
The catch-list clause [edit]
Using the catch-list clause, you provide one handle for every one type of exception. So, for instance if a guarded block generates a NullPointerException or an ArithmeticException, you have to provide two exception handling catch blocks - one for each exception. Refer to code listing 1.6 for an example.
The catch-any clause [edit]
There are times when too many blocks are just verbose, especially when all exceptions are handled in a similar manner. What you need here is a catch-any clause, where all exceptions are handled in a single catch block. Because all exceptions in Java are the sub-class of java.lang.Exception
class, you can have a single catch block that catches an exception of type Exception only. Hence the compiler is fooled into thinking that this block can handle any exception. Using this, the code in Listing 1.6 can be rewritten as follows:
Listing 1.8: The catch-any clause.
try { // ... } catch(Exception ex) { // Exception handling code for ANY exception }
The multi-catch clause [edit]
As of JDK 7, Java added another convenient feature in their exception handling routines - the multi-catch clause. This is a combination of the previous two catch clauses and let's you handle exceptions in a single handler while also maintaining their types. So, instead of being boxed into a parent Exception super-class, they retain their individual types.
Listing 1.9: The multi-catch clause.
try { // ... } catch(NullPointerException | NumberFormatException ex) { // Exception handling code specifically for the NullPointerException // and the NumberFormatException }
Example of handling exceptions [edit]
Let's examine the following code:
public void methodA() throws SomeException { // Method body } public void methodB() throws CustomException, AnotherException { // Method body } public void methodC() { methodB(); methodA(); }
In the code sample, methodC
is invalid. Because methodA
and methodB
pass (or throw) exceptions, methodC
must be prepared to handle them. This can be handled in two ways: a try
-catch
block, which will handle the exception within the method and a throws
clause which would in turn throw the exception to the caller to handle. The above example will cause a compilation error, as Java is very strict about exception handling. So the programmer forced to handle any possible error condition at some point.
A method can do two things with an exception. Ask the calling method to handle it by the throws
declaration. Or handle the exception inside the method by the try
-catch
block.
To construct a throws declaration, add throws ExceptionName
(additional exceptions can be added with commas). To construct a try
-catch
block, use the following syntax:
try { // Guarded region that will catch any exception that // occurs within this code. } catch(Exception ex) { // Caught exceptions are handled here } finally { // This clause is optional. Code here is executed // regardless of exceptions being thrown }
To work correctly, the original code can be modified in multiple ways. For example, the following:
public void methodC() throws CustomException, SomeException { try { methodB(); } catch(AnotherException e) { // Handle caught exceptions. } methodA(); }
The AnotherException
from methodB
will be handled locally, while CustomException
and SomeException
will be thrown to the caller to handle it.
Application Exceptions [edit]
Application Exception classes should extend the java.lang.Exception
class. Some of the JDK classes also throw exception objects inherited from java.lang.Exception
. If any of those Exception object is thrown, it must be caught by the application some point, by a catch-block. The compiler will enforce that there is a catch-block associated with an exception thrown, if the thrown exception object is inherited from java.lang.Exception
and it is not the java.lang.RuntimeException
or its inherited objects. However, java.lang.RuntimeException
or its inherited objects, can be caught by the application, but that is not enforced by the compiler.
Lets see what is the catching criteria for a catch block to catch the "thrown" exception.
A catch-block will "catch" a thrown exception if and only if:
- the thrown exception object is the same as the exception object specified by the catch-block
- the thrown exception object is the subtype of the exception object specified by the catch-block
try { throw new Exception("This will be caught below"); } catch(Exception ex) { // The thrown object is the same that what is specified at the catch-block } try { throw new NullPointerException("This will be caught below"); } catch(Exception e) { // NullPointerException is subclass of the Exception class. }
There can be more than one catch-block for a try-block. The catching blocks evaluated sequentially one by one. If a catch-block catch the exception, the others will not be evaluated.
Example:
try { throw new NullPointerException("This will be caught below"); } catch(Exception e) { // The NullPointerException thrown in the code above is caught here... } catch(NullPointerException e) { // ..while this code is never executed and the compiler returns an error }
Because NullPointerException
is subclass of the Exception
class. All NullPointerExceptions
will be caught by the first catch-block.
Instead the above code should be rewritten as follows:
try { throw new NullPointerException("This will be caught below"); } catch(NullPointerException e) { // The above NullPointerException will be caught here... } catch(Exception e) { // ..while other exception are caught here. }
Runtime Exceptions [edit]
The java.lang.RuntimeException
exception class is inherited from java.lang.Exception
. It is a special exception class, because catching this exception class or its subclasses are not enforced by the Java compiler.
- runtime exception
- Runtime exceptions are usually caused by data errors, like arithmetic overflow, divide by zero, ... . Runtime exceptions are not business related exceptions. In a well debugged code, runtime exceptions should not occur. Runtime exceptions should only be used in the case that the exception could be thrown by and only by something hard-coded into the program. These should not be able to be triggered by the software's user(s).
NullPointerException [edit]
NullPointerException
is a RuntimeException
. In Java, a special null
can be assigned to an object reference. NullPointerException
is thrown when an application attempts to use an object reference, having the null
value. These include:
- Calling an instance method on the object referred by a null reference.
- Accessing or modifying an instance field of the object referred by a null reference.
- If the reference type is an array type, taking the length of a null reference.
- If the reference type is an array type, accessing or modifying the slots of a null reference.
- If the reference type is a subtype of
Throwable
, throwing a null reference.
Applications should throw instances of this class to indicate other illegal uses of the null object.
Object obj = null; obj.toString(); // This statement will throw a NullPointerException
The above code shows one of the pitfall of Java, and the most common source of bugs. No object is created and the compiler does not detect it. NullPointerException
is one of the most common exceptions thrown in Java.
Why do we need null
? [edit]
The reason we need it is because many times we need to create an object reference, before the object itself is created. Object references cannot exist without a value, so we assign the null
value to it.
public Customer getCustomer() { Customer customer = null; try { ... customer = createCustomer(); ... } catch(Exception ex) { ... } return customer; }
In the above code we want to create the Customer inside the try-block, but we also want to return the object reference to the caller, so we need to create the object reference outside of the try-block, because of the scoping rule in Java. Incorrect error-handling and poor contract design can be a pitfall with any programming language, this is also true for Java.