Prev | Next |
Exceptions
An exception (or exceptional event) is a problem that arises during the execution of a program. When an exception occurs the normal flow of the program is disrupted and the program/application terminates abnormally, which is not recommended, therefore these exceptions are to be handled.
In Java, exception is an event that disrupts the normal flow of the program. It is an object which is thrown at runtime. We will see how to define a custom (or new) exception, how to throw it when something goes wrong, and how to handle it when it arises during the execution.
Defining a Exception class
Lets turn out attention to the Point class. Lets say we are working with points in the first quadrant only. This would mean the x- and y- coordinates are non-negative. At any point during the program execution we should never allow the x and y attributes of Point class to take negative values.
We will define a new Exception that can be used by Point class: NegativeCoordinateException
Define a class NegativeCoordinateException.java as follows:
public class NegativeCoordinateException extends Exception { NegativeCoordinateException(String s) { // Constructor which invokes super(s); // parent class' constructor } }
All exceptions are derived from Exception class. Exception class is part of the Java library.
Throwing an Exception object
An instance of this exception can be thrown when a x or y is assigned a negative value or when a computation results in these attributes becoming negative.
Now pertinent methods in Point class must be updated to throw this exception. Below we show how this is done for the setX() method of Point class.
class Point { : : public void setx(int xCoord) throws NegativeCoordinateException { if (xCoord >= 0) this.x = xCoord; else throw new NegativeCoordinateException("The coordinate cannot be negative"); } : : }
We create an instance of NegativeCoordinateException along with a suitable message and throw it if xCoord < 0. In a similar manner, the setY() method and the constructors need to be modified to throw this exception.
We also need to look at other methods that can potentially result rendering x or y negative. For example, the scalarMultiply() will result in negative coordinates if the argument passed is negative. A check needs to be added to this method and the exception needs to be thrown.
Exercise: Implement changes to all the pertinent methods by making necessary checks.
Catching & Handling an Exception
Now, any code that is invoking these methods of the Point class must catch the exception and decide the course of action. This is done witin a try { ... } catch () { .... } block. For example,
Point p = new Point(); try { p.setX(xVal); p.setY(yVal) } catch (NegativeCoordinateException nce) { // Implement an appropriate action here }
Catching of an exception happens when you include the call which can throw the said exception in try { ... } catch { ...} blocks. If x happens to be < 0, the control shifts to catch block. Otherwise catch block is not executed.
Typically there are 3 kinds of actions taken.
1. Simply exit the program by including "System.exit(0);" in the catch block.
2. Handle the Exception. You may print the message by calling nce.getMessage(). The method getMessage() is implemented in Exception class. And hence, it can be called from the subclass NegativeCoordinateException that we defined. This will print the message "The coordinate cannot be negative" that we have defined.
Alternately, you may also print the stack trace when the exception is caught. This is done by calling nce.printStackTrace(); in the catch block to enable the user to gain better insight in the problem.
3. Throw the exception in turn to its caller. Do not handle it.
// Driver.java public class Driver { public void test() throws NegativeCoordinateException { Point p = new Point(); p.setX(-3); // Not in try ... catch block } public static void main(String[] args) { Driver d = new Driver(); try { d.test(); } catch (NegativeCoordinateException nce) { nce.printStackTrace(); } } }
Now when setX(-3) throws an exception,the caller method test() does not handle it. Instead it throws to its caller. Either the caller may handle or it can in turn throw it. If none of the methods in the call hierarchy handle it, the program will exit. In the above case, the caller of test() method which is main() handles the exception.
In the absence of try ... catch block along the caller hierarchy, the program will terminate when the exception arises. But if we handle it, we can decide to take an alternative course of action and continue with it execution.
Checked vs. Unchecked Exceptions
Exceptions in Java are of two types. (i) Checked (ii) Unchecked. Checked exceptions have to be caught or thrown to its caller. If not, compiler reports an error.
For instance, in the above example, if you don't call setX() within a try ... catch block and fail to throw NegativeCoordinateException, the compiler will report an error. Try and check.
Any exception that extends Exception class fall in this category. The exception that we have defined NegativeCoordinateException is an example of checked exception.
Instead, if NegativeCoordinateException extended RuntimeException class, then try ... catch or throw is not mandatory. In the absence of try ... catch block, if setX() is passed a negative value, the exception will be thrown and but not handled causing the program to exit. This is an example of unchecked exception.
Is there a thumb rule to decide the type of exception
Generally, any exception, if it occured and not handled, will cause the program to incorrectly behave or affect the execution flow needs to be a checked exception. An exception which occurs only rarely and does not affect the program execution in a serious way can be unchecked.
IllegalCastException and NullPointerException are examples of unchecked exception (deriving from RuntimeException). When we manipulate any object, it could be null in which case a NullPointerException must be thrown. In OO world, everything is an object. Making NullPointerException checked would literally force the programmer to include 98% of the code in try block!! Similar reasoning applies to casting also.
Exception vs. Error
An exception is one which when handled properly cause the program execution flow to work correctly.
An error is one which, when it occurs, cannot be handled by any means. The only possibility for the program is to exit. In such a case, an error can be thrown. Unlike Exception, statements that cause an Error to be thrown are not enclosed within try block since there is no point catching it.
Defining a new type of error: Extend Error class (instead of Exception class). Other things remain the same.
public class SomeError extends Error { SomeError(String s) { super(s); } }
Throwing the Error instance: This is similar to how an Exception is thrown. Check and throw a new instance of an appropriate error type.
Handling the Error: Not possible!!!
JVM implements two Error types.
- OutOfMemoryError: Whenever any class is instantiated, it will fail if sufficient memory for the object instance cannot be allocated by the JVM. However, this is very rare scenario unless the programmer incorrectly specifies the exit condition of an iteration causing infinite instances of an object to be created.
- StackOverflowError: Whenever a method is called, it will fail if sufficient memory for the method call cannot be allocated by the JVM. However, this is is a vary rare scenario unless the programmer fails to specify the exit criteria for recursion causing infinite recursive calls.
Exercise: Try defining the exception by extending the RuntimeException and check the behavior.
Exceptions in Java libraries
Java libraries define several exceptions such as IOException, ArrayIndexOutOfBoundsException, NullPointerException, etc. These are thrown by certain methods which need to be caught and handled if you are using them.
How do you know if a particular method is going to throw an exception?
- The documentation generated by javadoc will have this information.
- Re-generate Java doc and check Point.html after the above updates to the class.
A try block can throw multiple exceptions
It is possible that a try block throws more than one exception. For instance, lets say we are trying to open a file. There are multiple ways this operation can fail.
- The file does not exist.
- No permission to access the file.
- The content of the file is garbled.
Hence, an exception for each failure needs to be defined and can be thrown by the method implementing file open. It can then be handled the following way.
try { // The constructor in File.java checks and throws these 3 exceptions File f = new File("/home/user/exception.txt"); } catch (FileNotFoundException fnfe) { // Handle it } catch (NoPermissionException npe) { // Handle it } catch (GarbledContentException gce) { // Handle it }
Exceptions can be nested
The try ... catch block can be nested. You can have a try...catch block inside another try or catch block. Any nesting depth is possible.
try { ..... try { ..... } catch (BTypeException bte) { ..... } } catch (ATypeException ate) { ..... try { ..... } catch (CTypeException cte) { ..... } }
A general exception can catch specific exceptions
In the previous section we noted that an instance of sub class can be assigned to an instance of super class. When we define a new exception, we extend from Exception class. Consider the following definition.
class ParticularException extends Exception { ...... }
Now, a catch clause that expects an Exception instance can also catch ParticularException instance. i.e.
try { ..... } catch (Exception e) { // catches Exception and ParticularException instances. ..... }
This has an importance consequence to the order of catch blocks that we specify. i.e. in case we have multiple catch clauses, the more specific ones should be specified before the less specific ones.
The wrong way | The correct way |
try { ..... } catch (Exception e) { // Handles both exceptions } catch (ParticularException pe) { // Unreachable code } |
try { ..... } catch (ParticularException pe) { // Handles particular exception } catch (Exception e) { // Handles other exceptions } |
The finally clause
Sometimes you may not want to handle an exception. Instead you may want to gracefully exit. A finally block can be used for this purpose. Let's say you opened a file/database and working with it in the try block and an exception is thrown. Let's say you don't have a catch block to handle the exception, but you want to close the file before exiting. The finally clause can come handy in this scenario.
try { // Open a file/database and do some processing // An exception is thrown } finally { // Close the file/database to avoid dangling updates }
You can also have try ... catch ... finally. Whenever execution of try block results in an exception, whether catch block exists to handle it or not, finally block will be executed.
A try block must be followed by either catch block(s) or finally block or both.