The Essence of OOP using Java, Exception Handling
Java Programming Notes # 1630
This series of lessons is designed to teach you about the essence of Object-Oriented Programming (OOP) using Java.
The first lesson in the series was entitled The Essence of OOP Using Java, Objects, and Encapsulation. The previous lesson was entitled The Essence of OOP using Java, The this and super Keywords.
You may find it useful to open another copy of this lesson in a separate browser window. That will make it easier for you to scroll back and forth among the different listings while you are reading about them.
This lesson explains Exception Handling in Java. The discussion includes the following topics:
- What is an exception?
- How do you throw and catch exceptions?
- What do you do with an exception once you have caught it?
- How do you make use of the exception class hierarchy provided by the Java development environment?
This lesson will cover many of the details having to do with exception handling in Java. By the end of the lesson, you should know that the use of exception handling is not optional in Java, and you should have a pretty good idea how to use exception handling in a beneficial way.
Stated simply, the exception-handling capability of Java makes it possible for you to:
- Monitor for exceptional conditions within your program
- Transfer control to special exception-handling code (which you design) if an exceptional condition occurs
The basic concept
This is accomplished using the keywords: try, catch, throw, throws, and finally. The basic concept is as follows:
- You try to execute the statements contained within a block of code. (A block of code is a group of one or more statements surrounded by braces.)
- If you detect an exceptional condition within that block, you throw an exception object of a specific type.
- You catch and process the exception object using code that you have designed.
- You optionally execute a block of code, designated by finally, which needs to be executed whether or not an exception occurs. (Code in the finally block is normally used to perform some type of cleanup.)
Exceptions in code written by others
There are also situations where you don't write the code to throw the exception object, but an exceptional condition that occurs in code written by someone else transfers control to exception-handling code that you write.
For example, the read method of the InputStream class throws an exception of type IOException if an exception occurs while the read method is executing. In this case, you are responsible only for the code in the catch block and optionally for the code in the finally block.
(This is the reason that you must surround the invocation of System.in.read() with a try block followed by a catch block, or optionally declare that your method throws an exception of type IOException.)
Exception hierarchy, an overview
When an exceptional condition causes an exception to be thrown, that exception is represented by an object instantiated from the class named Throwable or one of its subclasses.
Here is part of what Sun has to say about the Throwable class:
"The Throwable class is the superclass of all errors and exceptions in the Java language. Only objects that are instances of this class (or one of its subclasses) are thrown by the Java Virtual Machine or can be thrown by the Java throw statement. Similarly, only this class or one of its subclasses can be the argument type in a catch clause."
Sun goes on to say:
"Instances of two subclasses, Error and Exception, are conventionally used to indicate that exceptional situations have occurred. Typically, these instances are freshly created in the context of the exceptional situation so as to include relevant information (such as stack trace data)."
The Error and Exception classes
The virtual machine and many different methods in many different classes throw exceptions and errors. I will have quite a lot more to say about the classes named Error and Exception later in this lesson.
Defining your own exception types
You may have concluded from the Sun quotation given above that you can define and throw exception objects of your own design, and if you did, that is a correct conclusion. (Your new class must extend Throwable or one of its subclasses.)
The difference between Error and Exception
As mentioned above, the Throwable class has two subclasses:
What is an error?
What is the difference between an Error and an Exception? Paraphrasing David Flanagan and his excellent series of books entitled Java in a Nutshell, an Error indicates that a non-recoverable error has occurred that should not be caught. Errors usually cause the Java virtual machine to display a message and exit.
Sun says the same thing in a slightly different way:
"An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions."
For example, one of the subclasses of Error is named VirtualMachineError. This error is "Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating. "
What is an exception?
Paraphrasing Flanagan again, an Exception indicates an abnormal condition that must be properly handled to prevent program termination.
Sun explains it this way:
"The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch."
As of JDK 1.4.0, there are more than fifty known subclasses of the Exception class. Many of these subclasses themselves have numerous subclasses, so there is quite a lot of material that you need to become familiar with.
The RuntimeException class
One subclass of Exception is the class named RuntimeException As of JDK 1.4.0, this class has about 30 subclasses, many which are further subclassed. The class named RuntimeException is a very important class.
The RuntimeException class, and its subclasses, are important not so much for what they do, but for what they don't do. I will refer to exceptions instantiated from RuntimeException and its subclasses as unchecked exceptions.
Basically, an unchecked exception is a type of exception that you can optionally handle, or ignore. If you elect to ignore the possibility of an unchecked exception, and one occurs, your program will terminate as a result. If you elect to handle an unchecked exception and one occurs, the result will depend on the code that you have written to handle the exception.
All exceptions instantiated from the Exception class, or from subclasses of Exception other than RuntimeException and its subclasses must either be:
- Handled with a try block followed by a catch block, or
- Declared in a throws clause of any method that can throw them
In other words, checked exceptions cannot be ignored when you write the code in your methods. According to Flanagan, the exception classes in this category represent routine abnormal conditions that should be anticipated and caught to prevent program termination.
Checked by the compiler
Your code must anticipate and either handle or declare checked exceptions. Otherwise, your program won't compile. (These are exception types that are checked by the compiler.)
Throwable constructors and methods
As mentioned above, all errors and exceptions are subclasses of the
Throwable class. As of JDK 1.4.0, the Throwable class provides
four constructors and about a dozen methods. The four constructors are
shown in Figure 1.
Throwable(String message, Throwable cause)
Throwable(Throwable cause) Figure 1
The first two constructors have been in Java for a very long time. Basically, these two constructors allow you to construct an exception object with, or without a String message encapsulated in the object.
New to JDK 1.4
The last two constructors are new in JDK 1.4.0. These two constructors are provided to support the cause facility. The cause facility is new in release 1.4. It is also known as the chained exception facility. (I won't cover this facility in this lesson. Rather, I plan to cover it in a series of lessons that I am developing having to do with the myriad of new features in JDK 1.4.)
Methods of the Throwable class
Figure 2 shows some of the methods of the Throwable class. (I
omitted some of the methods introduced in JDK 1.4 for the reasons given above.)
fillInStackTrace() getStackTrace() printStackTrace(). setStackTrace(StackTraceElement stackTrace)
The first four methods in Figure 2 deal with the StackTrace. In case you are unfamiliar with the term StackTrace, this is a list of the methods executed in sequence that led to the exception. (This is what you typically see on the screen when your program aborts with a runtime error that hasn't been handled.)
The two methods dealing with messages provide access to a String message that may be encapsulated in the exception object. The getMessage class simply returns the message that was encapsulated when the object was instantiated. (If no message was encapsulated, this method returns null.)
The getLocalizedMessage method is a little more complicated to use. According to Sun, "Subclasses may override this method in order to produce a locale-specific message."
The toString method
The toString method is inherited from the Object class and overridden in the exception subclass to "return a short description of the Throwable".
All exception objects inherit the methods of the Throwable class, which are listed in Figure 2. Thus, any of these methods may be invoked by the code in the catch block in its attempt to successfully handle the exception.
For example, exceptions may have a message encapsulated in the exception object, which can be accessed using the getMessage method. You can use this to display a message describing the error or exception.
You can also use other methods of the Throwable class to:
- Display a stack trace showing where the exception or error occurred
- Produce a String representation of the exception object
So, what is an exception?
According to the online book entitled The Java Tutorial by Campione and Walrath:
"The term exception is shorthand for the phrase "exceptional event". It can be defined as follows: Definition: An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions."
When an exceptional condition occurs within a method, the method may instantiate an exception object and hand it off to the runtime system to deal with it. This is accomplished using the throw keyword. (This is called throwing an exception.)
To be useful, the exception object should probably contain information about the exception, including its type and the state of the program when the exception occurred.
Handling the exception
At that point, the runtime system becomes responsible for finding a block of code designed to handle the exception.
The runtime system begins its search with the method in which the exception occurred and searches backwards through the call stack until it finds a method that contains an appropriate exception handler (catch block).
An exception handler is appropriate if the type of the exception thrown is the same as the type of exception handled by the handler, or is a subclass of the type of exception handled by the handler.
Thus, the requirement to handle an exception that has been thrown progresses up through the call stack until an appropriate handler is found to handle the exception. If no appropriate handler is found, the runtime system and the program terminate.
(If you have ever had a program terminate with a NullPointerException, then you know how program termination works).
According to the jargon, the exception handler that is chosen is said to catch the exception.
Advantages of using exception handling
According to Campione and Walrath, exception handling provides the following advantages over "traditional" error management techniques:
- Separating Error Handling Code from "Regular" Code
- Propagating Errors Up the Call Stack
- Grouping Error Types and Error Differentiation
Separating error handling code from regular code
I don't plan to discuss these advantages in detail. Rather, I will simply refer you to The Java Tutorial and other good books where you can read their discussions. However, I will comment briefly.
Campione and Walrath provide a good illustration where they show how a simple program having about six lines of code get "bloated" into about 29 lines of very confusing code through the use of traditional error management techniques. Not only does the program suffer bloat, the logical flow of the original program gets lost in the clutter of the modified program.
They then show how to accomplish the same error management using exception handling. Although the version with exception handling contains about seventeen lines of code, it is orderly and easy to understand. The additional lines of code do not cause the original logic of the program to get lost.
You must still do the hard work
However, the use of exception handling does not spare you from the hard work of detecting, reporting, and handling errors. What it does is provide a means to separate the details of what to do when something out-of-the-ordinary happens from the normal logical flow of the program code.
Propagating exceptions up the call stack
Sometimes it is desirable to propagate exception handling up the call stack and let the corrective action be taken at a higher level.
For example, you might provide a class with methods that implement a stack. One of the methods of your class might be to pop an element off the stack.
What should your program do if a using program attempts to pop an element off an empty stack? That decision might best be left to the user of your stack class, and you might simply propagate the notification up to the calling method and let that method take the corrective action.
Grouping exception types
When an exception is thrown, an object of one of the exception classes is passed as a parameter. Objects are instances of classes, and classes fall into an inheritance hierarchy in Java. Therefore, a natural hierarchy can be created, which causes exceptions to be grouped in logical ways.
For example, going back to the stack example, you might create an exception class that applies to all exceptional conditions associated with an object of your stack class. Then you might extend that class into other classes that pertain to specific exceptional conditions, such as push exceptions, pop exceptions, and initialization exceptions.
When your code throws an exception object of a specific type, that object can be caught by an exception handler designed either to:
- Catch on the basis of a group of exceptions, or
- Catch on the basis of a subgroup of that group, or
- Catch on the basis of one of the specialized exceptions.
In other words, an exception handler can catch exceptions of the class specified by the type of its parameter, or can catch exceptions of any subclass of the class specified by the type of its parameter.
More detailed information on exception handling
As explained earlier, except for Throwable objects of type Error and for Throwable.Exception objects of type RuntimeException, Java programs must either handle or declare all Exception objects that are thrown. Otherwise, the compiler will refuse to compile the program.
In other words, all exceptions other than those specified above are checked by the compiler, and the compiler will refuse to compile the program if the exceptions aren't handled or declared. As a result, exceptions other than those specified above are often referred to as checked exceptions.
Catching an exception
Just to make certain that we are using the same terminology, a method catches an exception by providing an exception handler whose parameter type is appropriate for that type of exception object. (I will more or less use the terms catch block and exception handler interchangeably.)
The type of the parameter in the catch block must be the class from which the exception was instantiated, or a superclass of that class that resides somewhere between that class and the Throwable class in the inheritance hierarchy.
Declaring an exception
If the code in a method can throw a checked exception, and the method does not provide an exception handler for the type of exception object thrown, the method must declare that it can throw that exception. The throws keyword is used in the method declaration to declare that it throws an exception of a particular type.
Any checked exception that can be thrown by a method is part of the method's programming interface (see the read method of the InputStream class, which throws IOException, for example). Users of a method must know about the exceptions that a method can throw in order to be able to handle them. Thus, you must declare the exceptions that the method can throw in the method signature.
Checked exceptions are all exception objects instantiated from subclasses of the Exception class other than those of the RuntimeException class.
Exceptions of all Exception subclasses other than RuntimeException are checked by the compiler and will result in compiler errors if they are neither caught nor declared.
You will learn how you can create your own exception classes later. Whether your exception objects become checked or not depends on the class that you extend when you define your exception class.
(If you extend a checked exception class, your new exception type will be a checked exception. Otherwise, it will not be a checked exception.)