HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Introduction
With ever increasing software complexity, error handling mechanisms offered by programming languages become more and more important. Traditional error handling techniques such as using global variables to indicate an error (errno in C), returning a value that represents an error or simply terminating the program resulted in difficult to read and error-prone code.

The alternative to these techniques was introduced in C++. The exception mechanism offered an elegant error handling technique that quickly became the primary tool for dealing with errors in OO languages. The concept of exception handling is based on separating error processing from the rest of the code and redirecting the flow of control to the exception block should an error occur. Given the ability to "catch", "throw" and propagate the exception, developers have a powerful tool to create robust and encapsulated components.

Although Java's exception mechanism was originally derived from C++, there are several key differences that every Java programmer should be aware of. Since many Java developers usually have a strong C++ background, they tend to re-use' their C++ coding techniques in Java. While syntactically correct, this approach frequently results in hybrid and ineffective code. To better understand the underlying issues, we will take a closer look at the C++ implementation of exception handling first.

C++ Exceptions
In C++, any function or method that is unable to deal with an error can throw (or raise) an exception. This will result in reverting the control flow back to the caller who is responsible for handling the exception. The caller would enclose code that can generate the exception in a try block and provide one or more catch blocks that are designated to handle the error.

The real power of C++ exceptions is closely related to one of the most tedious tasks in C++: memory management. When an exception is thrown and the control flow is re-directed back to the caller, the call stack unwinding begins and all local objects are deleted and their destructors called. This process continues until the matching catch block is found. Otherwise, function terminate is called to abort the execution.

The C++ exception mechanism, however, has its limitations. Not surprisingly, most of the problematic issues are also related to memory management. While it is true that all local objects get destroyed during stack unwinding, none of the dynamically allocated memory is reclaimed. Listing 1 shows C++ function WillLeakMemory that instantiates two objects of classes fstream and DatabaseConnection. We assume that, as with any well-behaved database class, our DatabaseConnection throws an exception if any database-related errors occur (e.g. time-out, lost or busy connection, invalid password). Given that something really goes wrong with the database connection, the exception is thrown and no dynamically created objects will get deleted. There are, of course, programming techniques that address this problem. This includes handling exceptions locally (de-allocate resources only after all possible exceptions are caught), catching exceptions locally, deleting resources and then re-throwing the exception or using a resource manager that will be allocated on the stack and will de-allocate all resources in its destructor. None of these solutions are simple, or convenient.

More problems can occur when throwing an exception in the C++ constructor. The memory for the object itself has already been allocated by the time the constructor is called, which usually results in the object remaining allocated.

Finally, C++ exception specifications are optional and no compile-time checking is performed. The following function declarations indicate that Fun1() can throw none or any defined exception and Fun2() does not throw any exceptions.

void Fun1();
void Fun2() throw();

To illustrate that there is no compile-time checking performed, consider the code in Listing 2. Despite the obvious inconsistency between specification and implementation of void foo(), the code will compile without any errors or warnings.

Why would designers of the language decide to implement exception handling this way? To understand why, we must first understand the fundamental premise of C++: Provide a developer with powerful tools and assume that they know what they are doing. If you keep this in mind, you will have no difficulties using exceptions in C++ and will enjoy all the benefits and power they provide.

Java Exceptions
By now, you should have a good understanding of what exceptions do and how they are used in C++. Let's take a look at how exception handling is implemented in Java and see how language designers dealt with the issues and challenges as we investigated them in the previous section.

Syntactically, Java exceptions are very similar to the C++ implementation. There is the familiar throw statement, try and catch blocks. The idea remains the same: provide a mechanism for separating the error handling routine from the rest of the code and initiate the control flow transfer whenever an error occurs. Java exception handling, however, goes far beyond the basics. Following is an explanation of the features that make Java exceptions a powerful part of the language.

Exception Hierarchy
All possible exceptions in Java extend the Throwable class from the java.lang package. Figure 1 shows that there are two direct subclasses of Throwable: Exception and Error.

Figure 1
Figure 1:

You should not attempt to catch instances of the Error class or its subclasses because they generally indicate an unrecoverable error such as LinkageError, ThreadDeath or VirtualMachineError. The Exception class is the superclass of all exceptions in the traditional C++ sense. All standard, pre-defined exception classes in Java extend the Exception class, including RuntimeException class. As its name suggests, the RuntimeException class and its subclasses are used to indicate the runtime errors such as division by zero (ArithmeticException), invalid cast (ClassCastException) or passing an illegal argument (IllegalArgumentException).

Compile-Time Checking
As you probably know, Java performs compile-time checking of exceptions, but what exactly does that mean? Do all subclasses of Throwable require catch block? Certainly not. Any of the Error or RuntimeException exceptions can occur at any point in the program and can be unrecoverable. Therefore, Error, RuntimeException and their subclasses are also known as unchecked exceptions and are exempt from compile-time checking. All other exceptions are checked exceptions for which the developer must provide corresponding specifications and exception handlers.

Compile-time checking also has its disadvantages. For example, assume the programmer takes all measures to ensure that no errors will occur. Nevertheless, if any exceptions were specified in method declarations, the programmer is still forced to provide try and catch blocks. Therefore, methods should not throw exceptions just to return values or if the error can be processed locally. Doing so will result in redundancy and inefficient code.

Throw - Catch - Finally
In Java, every try block can have one or more catch blocks and a finally block. When the try block is exited for any reason (normal execution or exception was thrown) the finally block is executed. If the matching catch block exists when exception is thrown, it is executed and the control flow is reverted to the finally clause. This mechanism nicely solves the problem with releasing or cleaning-up resources such as file, semaphore or database connection. If a method acquires the resource, it must be released upon execution of the try block regardless the reason for the exit. The code in Listing 3 demonstrates this technique.

Inheritance and Interfaces
Because Java enforces compile-time checking of exceptions, special rules apply when subclasses override or implement methods with exception specifications. When extending a class, an overriding method cannot throw any exceptions that are not allowed in the superclass declaration. Listing 4 shows class myTestSuper with two methods that are overridden in the class myTestChild. However, the myTesterChild.DoSomeWork2() specification "breaks" the declaration of myTestSuper.DoSomeWork2() by throwing SQLExceptions and thus will result in compile-time errors.

Similar rules are enforced when exceptions are specified in interfaces. If an interface specifies exceptions in its declaration, all implementations must comply to this "contract". When one class method implements more that one interface method, the overriding declaration must be compatible with all interface declarations. For example, Listing 5 will result in the compile-time error since the Impl class declaration "breaks" the interface A exception specification.

Conclusion
Error handling mechanisms have become one of the most important language features. Introduced in C++, exceptions provide enhanced capabilities to control error processing by separating error handling from the rest of the code. This enables us to develop robust and fully encapsulated software components. C++ exceptions, however, leave a programmer with a number of challenges: no compile-time checking, memory management, throwing exceptions in constructors.

Java's implementation of exception handling introduces several new features: Java exception hierarchy, try-catch-finally construct and compile-time checking. Java exceptions are easy to use and can be a very powerful tool. However, as with any language feature, exceptions must be used properly. Creating complicated hierarchies and declaring unnecessary exceptions can produce redundant and inefficient code.

It is important to understand that no exception mechanism is perfect. If you want to have more control to make your code robust and easy to use, you will appreciate C++ exceptions. On the other hand, if you want to implicitly enforce certain design rules and are not concerned with some redundant code, you will like Java's implementation of exception handling. In any case, understanding the underlying principles of exception handling will help you design robust and effective code regardless of the language you use.

References
Arnold, K., and Gosling, J., The Java Programming Language, Addison-Wesley, Reading, MA, 1996.
Cornell, G., and Horstmann, C. S., Core JAVA, Prentice Hall, Upper Saddle River, NJ, 1996.
Gosling, J.,Joy, B., and Steele, G., The Java Language Specification 1.0, 1996.
Meyers, S., Effective C++: 50 Specific Ways to Improve Your Programs and Designs, Addison-Wesley, Reading, MA, 1992.
Stroustrup, B., The C++ Programming Language, Third Edition, Addison-Wesley, Reading, MA, 1997.

About the Author
Maros Cunderlik is a consultant for Connect Computer Co., a regional consulting and system integration firm based in Minneapolis, MN. He focuses on OO design and distributed object architecture. Maros can be reached at maros.cunderlik@connects.com

	

Listing 1: C++ Exceptions and Memory Management.
 
//Function WillLeakMemory 
void  WillLeakMemory () { 

  //allocate some resources dynamically 
  fstream* ptrFile = new fstream(); 
  DatabaseConnection* ptrDB = new DatabaseConnection(); 
  //attempt to open Database Connection - can throw an exception 
  ptrDB->Open(); 

  //do some work 

  //close connection and delete resources 
  ptrDB->Close(); 
  delete ptrDB; 
  delete ptrFile; 
} 

Listing 2. C++ exception specifications are not checked at compile-time.
 
//Note: this code will compile with no errors 
//Exception class 
class Exception { 
public: 
  Exception(): strError("") {}; 
  Exception( const char* strErrorText ) : strError( strErrorText ) {}; 
  const char* GetErrorText() const { return strError; }; 
private: 
  const char* strError; 
}; 

//Function foo 
//throws exception despite of its specification 
void foo() throw(){  
  throw(Exception( "hey")); 
} 

//main - call foo 
main() { 
  foo(); 
  return 1; 
} 

Listing 3.
 
public class foo 
{ 
  public void doSomeWork() 
  { 
    try 
    { 
      //do some work 
    } 
    finally 
    { 
      //clean-up the vector 
      fooVector.removeAllElements(); 
    } 
  } 
  private myVector fooVector; 
} 

Listing 4: Overriding Methods and Exception Specifications
 
//myException class 
class myException extends Exception 
{ 
  myException()  
  { 
    super();  
  } 
  myException( String strErrorText ) 
  { 
    super( strErrorText ); 
  } 
 } 

//myTestSuper 
class myTestSuper 
{ 
  public void DoSomeWork1()  
    throws SQLException, myException 
  { 
    //do some work 
  } 
  public void DoSomeWork2()  
    throws myException 
  { 
    //do some work 
  } 
} 

//myTestChild - overrides both methods of myTestSuper 
class myTestChild extends myTestSuper 
{ 
  public void DoSomeWork1()  
    throws myException //will compile O.K. 
  { 
    //do some work 
  } 
  public void DoSomeWork2()  
    throws SQLException //will result in compile error 
  { 
    //do some work 
  } 
} 

Listing 5.
 
interface A{ 
  public void Work() throws SQLException; 
} 
interface B { 
  public void Work() throws SQLException, ClassNotFoundException; 
} 
class Impl implements A,B { 
  public void Work() throws ClassNotFoundException //Error!!! 
  { 
    //do some work 
  } 
}


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.