SCOUG OS/2 For You - July 1999
Cup of Java
Exceptions Are the Rule
One of the hardest disciplines to follow in programming is proper handling of error conditions or, to be more precise, unexpected processing results. For procedural languages, this situation is somewhat understandable since proper coding usually means testing each API call for its return code and then nesting one or more IF statements around the API calls. For example, if you need to invoke a set of three API functions, each of which can return a nonzero error status, the code would be something like:
rc1 = api_one(...);
if (rc1 == 0) { //normal return
...some processing...
rc2 = api_two(...);
if (rc2 == 0) { //normal return
... some processing ...
rc3 = api_three(...);
if (rc3 == 0) { //normal return
... some processing ...
}
else { //error handling for third call
... some processing...
}
}
else { //error handling for second call
... some processing ...
}
}
else { //error handling for first call
... some processing ...
}
} //end if logic
This type of coding is hard to follow and maintain plus, if there is common logic for the error handling pieces it can be even more confusing as you would use flags or test the return code values in more complex IF statements.
Java Approach
Java addresses this issue by providing mechanisms that allow you to write your code based on the "normal" processing that you expect but still being able to handle abnormal situations. Because the processing for the abnormal cases is outside of the normal logic, the code is much easier to follow and maintain plus it is also easy to identify what code is supposed to be executed for the various error conditions.
All of this accomplished by the use of the Exception class and the related language statement "try". The basic idea is:
- There are any number of statements and invoked methods that can fail due to processing errors, runtime conditions, bad data, etc. You want to be able to write your code so that if none of these abnormalities occur, your code will just continue to execute. If an abnormal condition (or "exception") occurs, then you want your code to stop executing and, instead, some other section of code should execute that has been defined to handle that exception.
To invoke this mechanism, you simply include the processing code within the context of a "try" statement and don't worry about the exceptions (for now). For example, the code fragment given above would look much simpler (and we can remove the return codes for the purpose of error handling):
try {
api_one(...);
... some processing ...
api_two(...);
... some processing ...
api_three(...);
... some processing ...
}
When this code is executed, as long as no exceptions occur, it will just continue to execute down to the end of the code block. But what if there are exceptions?
To handle exceptions, you must add a second portion of code to the example. This new section, labelled the "catch" portion identifies which exceptions you are going to handle and the specific code to handle each one. So, let's say that your api functions can create two different exceptions: Exception1 and Exception2. The "catch" code would be:
try {
..same as above..
}
catch (Exception1 excpt) {
..processing for Exception1
}
catch (Exception2 excpt) {
..processing for Exception2
}
This code structure is much more intuitive than the nested IF statements and is also easier to maintain and extend. For example, if you added a new exception into the Api function, you would just need to add a new catch clause into your coding.
If you wanted to code just one catch clause to handle all additional exceptions, you could also just add it to the end since the logic used by the Java runtime is to step down the list of catch clauses until it finds one that matches the exception object it is trying to process. That clause would be:
catch(Exception x) {
..processing
}
This works because all exceptions are subclasses of Exception and so are included in casting to Exception.
You might wonder: what if I have some set of code that needs to be executed always whether the exception occurs or not? This can be accomodated by adding another clause to the code called the "finally" clause. The code in this clause will always be executed after the "try" and/or "catch" clauses are executed. The format is:
try {
..same as above
}
catch() {
..same as above
}
finally {
..processing
}
Another question related to this might be: "what happens if an exception occurs that is not accounted for in the list of catch clauses?". There are two answers depending on the type of exception:
- the simple answer is that you will not be able to compile your program in this case. For instance, if the api_one method can cause Exception1 and you leave out the first catch clause, you will get a compile time error! So, Java does not let you code without catching all exceptions that can occur from the code. This will also happen if you just code: api_one(...); without it being inside of a "try" structure.
- the more complete answer is that certain types of errors are allowed to occur without compile- time checks. For example, if you have an object reference, such as:
String x;
and you reference x outside of a "try" structure, the compiler will allow it and, during execution, if you have not initialized x, you will get an runtime error with "Null Pointer Exception". However, in general, you should use the "try" structure just for safety.
Passing Exceptions
Another common situation is to have nested method calls. In these cases, you may not want to process the exception in the lower level methods but simply pass them back to the higher level ones. Java provides for this by allowing you to explicitly specify when defining a method that you want this behavior; it is accomplished by adding a "throws ..." clause to the method definition. For example, the api_one method might call api_one_one or other methods that can produce Exception2 errors, but api_one does not contain code to handle them. So, its definition might be:
public void api_one(...) throws Exception2 {
...
api_one_one();
...
}
This will get past the compile check since the method has specified to the compiler that it wants to pass Exception2 occurrences back to its caller and it also alerts the compiler that any caller of api_one must handle Exception2 occurrences. So, in this case, you do not need the "try" structure. However, you could still put the reference into a "try"structure and then the Exception2 object would not be passed back to your invoker.
Custom Exceptions
Of course, this whole concept would not be complete unless it also allowed you to create your own exceptions. This is quite easy since, after all, the Exception class can be extended. You simply create your own class as an extension of Exception and provide whatever logic you need, usually in the constructor and get/set methods for any properties that are needed by your exception. As an example, suppose you were writing a database interface class and wanted to have exception processing in the method that added a name to the database if that name were already present. You might create a simple exception class such as:
DuplicateNameException extends Exception {
private String name;
public DuplicateNameException(String dupname) {
super();
name = dupname;
}
public String getName() {
return name;
}
}
Then, in the addName method of the database interface class, you would code something like:
public void addName(String name) throws DuplicateNameException {
...
... call database api to add the name
... test for name already defined:
if (name defined) {
throw new DuplicateNameException(name);
}
...
}
and, in the code which invokes addName:
try {
...
addName("testing");
...
}
catch (DuplicateNameException) {
...
}
Summary
Exception handling in Java allows for natural coding with ease of maintenance and readibility. Since it is based on an object-oriented imple-mentation of Exception classes, it is also very easy to extend by defining and using custom exception objects. Also, since the compiler enforces exception handling code rules, it automatically forces you, the programmer, to be aware of and provide for the exceptions that can arise from your coding.
Next month we will look at packaging and how classes are located both at compile and run time.
Cup of Java by Terry Warren is a series that started in April 1999. Prior articles are:
The Southern California OS/2 User Group
P.O. Box 26904
Santa Ana, CA 92799-6904, USA
Copyright 1999 the Southern California OS/2 User Group. ALL RIGHTS
RESERVED.
SCOUG, Warp Expo West, and Warpfest are trademarks of the Southern California OS/2 User Group.
OS/2, Workplace Shell, and IBM are registered trademarks of International
Business Machines Corporation.
All other trademarks remain the property of their respective owners.
|