Architecture & DesignObject Integrity & Security: Error & Exceptions

Object Integrity & Security: Error & Exceptions

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

This is the thirty-eighth installment in a series of articles about fundamental object-oriented (O-O) concepts and related object-oriented technologies. The material presented in these articles is based on material from the second edition of my book, The Object-Oriented Thought Process, 2nd edition. The Object-Oriented Thought Process is intended for anyone who needs to understand basic object-oriented concepts and technologies before jumping directly into the code. Click here to start at the beginning of the series.

In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems Web site http://java.sun.com/. You can download the standard edition, J2SE 5.0, at http://java.sun.com/j2se/1.5.0/download.jsp to compile and execute these applications. I often reference the Java J2SE 5.0 API documentation and I recommend that you explore the Java API further. Code listings are provided for all examples in this article as well as figures and output (when appropriate). See the first article in this series for detailed descriptions for compiling and running all the code examples (http://www.developer.com/design/article.php/3304881).

The code examples in this series are meant to be a hands-on experience. There are many code listings and figures of the output produced from these code examples. Please boot up your computer and run these exercises as you read through the text.

For the past several months, you have been exploring various issues regarding object integrity, security, and performance. In last month’s article, you covered some of the basic techniques for handling anomalies. Object-oriented languages generally use exception-handling models that facilitate the handling of these types of anomalies.

In this month’s article, you will delve deeper into the question of what an exception actually is and how it differs from an error.

Exception or Error?

As you saw last month, the Exception class is not at the top of the anomaly inheritance tree. In fact, both errors and exceptions inherit from a class called Throwable, as seen in Diagram 1.

Diagram 1: The Throwable inheritance tree.

In last month’s article, I defined a basic rule-of-thumb for distinguishing between an Error and Exception: An error tends to be an anomaly that is so serious that the application can’t continue. This may be something such a hardware failure or a memory issue that corrupts the execution of an application.

The definition from the Java API specifications describes the Throwable, Error, and Exception classes as follows:

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.

http://java.sun.com/j2se/1.5.0/docs/api/

The Error class:

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. The ThreadDeath error, although a “normal” condition, is also a subclass of Error because most applications should not try to catch it.

http://java.sun.com/j2se/1.5.0/docs/api/

The Exception class:

The Exception class and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch.

http://java.sun.com/j2se/1.5.0/docs/api/

To illustrate, take a look at a simple program that purposely generates an anomaly.

public class Anomaly {

   public static void main(String args[]){

      int a = 0, b = 1, c = 0;

      c = a/0;

      System.out.println("Exit");

   }

}

Listing 1: Generating an anomaly.

In this case, when the the code encounters the divide by zero, the application does not catch it and the exception finds its way to the run-time and terminates the application ungracefully as seen in Figure 1.

Figure 1: Generating an anomaly.

There is enough information presented when the application crashes that you can see that the system has recognized an ArithmeticException.

Because this is the first anomaly you will explore (actually an Exception), it is interesting to inspect the inheritance tree of the ArithmeticException.

java.lang.Object
   java.lang.Throwable
      java.lang.RuntimeException
         java.lang.ArithmeticException

ArithmeticException actually is a direct descendant of RunTimeException.

Here is the API definition for the RunTimeException:

public class RuntimeException
extends Exception

RuntimeException is the superclass of those exceptions that can be thrown during the normal operation of the Java Virtual Machine.

A method is not required to declare in its throws clause any subclasses of RuntimeException that might be thrown during the execution of the method but not caught.

One of the important issues here is defined in the second sentence. For a RunTimeException, you do not have to do any preparation for it in your code. In short, this means that the compiler does not require you to provide code to catch a possible occurrence of a RunTimeException. However, this does not mean that you shouldn’t—it is just that the compiler will not enforce this.

Consider the IOException as described by the Java API:

java.lang.Object
   java.lang.Throwable
      java.lang.Exception
         java.io.IOException

Note that in this case the IOException does not inherit from RunTimeException; it is a direct descendant of Exception. In the case of IOException, the compiler will force you to prepare for this in the sense that you will have to provide code to catch or throw the IOException. To illustrate, consider the following code in Listing 2:

import java.io.*;

class OpenFile {
   public static void main(String args[]) {

      FileInputStream fileStream = new
         FileInputStream("Test.txt");

      DataInputStream fileIn =
         new DataInputStream(fileStream);

      while (fileIn .available() !=0) {

         System.out.println (fileIn .readLine());

      }

      fileIn .close();

   }

}

Listing 2: Generating an IOException.

When you compile this code, you receive the output in Figure 2.

Figure 2: Generating an IOException.

Note that the compiler is complaining about the fact that the code must catch or throw certain exceptions. This is in contrast to the ArithmeticException that was not required to be caught. In this case, the compiler gets even more specific and indicates the specific exception that needs to be dealt with, the FileNotFoundException.

The interesting point here is that the compiler is actually trying to help you with the integrity, and perhaps security, of your code. Imagine the things that could possibly go wrong when you attempt to open a file. The obvious problem would be that the file may not even exist. Or, if you are opening it up across a network, the network connection possibly could be down.

Although the code might work perfectly well in a test environment or even 99% of the time in a production environment, there is always a chance that the file might not be available. If you don’t provide contingency code for this situation, the application will become unstable, if it doesn’t ultimately crash.

As the error message indicates, you have two options to get your code to compile: throw or catch the exception.

Throwing the Exception

In this example, the option of throwing the exception will allow you to compile the application. The code is presented in Listing 3. Note the inclusion of the throws IOException statement in the definition of the main( ) method.

import java.io.*;

class OpenFile {
   public static void main(String args[]) throws IOException {

      FileInputStream fileStream= new
         FileInputStream("Test.txt");

      DataInputStream fileIn =
         new DataInputStream(fileStream);

      while (fileIn .available() !=0) {

         System.out.println (fileIn .readLine());

      }

      fileIn .close();

   }

}

Listing 3: Throwing the Exception.

When the code is compiled, you get the output in Figure 3.

Figure 3: Throwing the Exception.

Although this code will compile, in reality it circumvents the intent of the compiler checks regarding these types of exceptions. The issue is that the code will not properly handle the IOException if and when it actually does occur. All the application does in this case is to pass the exception along to the runtime environment. And you all know what will happen then—it will terminate the application as seen in Figure 4.

Figure 4: The file is not found.

As you have learned over and over, no matter what happens, you must try and make sure that the application never crashes. Thus, simply throwing exceptions must be done with care. In practice, throwing an exception in the main ( ) method is dangerous and you should only throw an exception when you know that another method up the hierarchy in your application will indeed deal with it.

Catching the Exception

In fact, the best way to deal with the situation in the example is to catch this exception. The code for this is Listing 4, which does produce a clean compile.

import java.io.*;

class OpenFile {
   public static void main(String args[]) {

      try {

         FileInputStream fileStream= new
            FileInputStream("Test.txt");

         DataInputStream fileIn =
            new DataInputStream(fileStream);

         while (fileIn .available() !=0) {

            System.out.println (fileIn .readLine());

         }

         fileIn .close();

      } catch (Exception e) {

         System.out.println("Exception caught");

      }

   }

}

Listing 4: Throwing the Exception.

When this code is executed, the FileNotFoundException is still generated; however, the application itself catches the exception and handles it gracefully. By this, I mean that the application catches the exception, deals with it, and then allows the application to exit without crashing, In fact, there is really no need to exit the application at all. For example, it is possible that the reason that the file can’t be found is due to user error.

java.lang.Object
   java.lang.Throwable
      java.lang.Exception
         java.io.IOException
            java.io.FileNotFoundException
Consider the possibility that the user entered a typo and misspelled the name of the file. After catching the exception, you could in fact prompt the user to reenter the name. Once the proper name is entered, the program then can go about its merry way as if nothing wrong ever happened.

Exception Granularity

The example in Listing 4 is meant to illustrate the capture of a FileNotFoundException. However, what is actually happening is that the code is catching all exceptions.

import java.io.*;

class OpenFile {

   public static void main(String args[]) {

      System.out.println("Begin Application");

      try {

         FileInputStream fileStream= new
            FileInputStream("Test.txt");

         DataInputStream fileIn =
            new DataInputStream(fileStream);

         while (fileIn .available() !=0) {

            System.out.println (fileIn .readLine());

         }

         fileIn .close();

     } catch (java.io.IOException  e) {

         System.out.println("IOException caught");

      }

   }

}

Listing 5: Catching an IOException Exception.

Now, when this application is executed, the only exception that is caught is the IOException. Figure 5 shows that the error message now specifically indicates that the exceptions handled were of the IOException class. This approach allows you to be more granular in your exception handling. Although you don’t necessarily want to let exceptions go uncaught, this approach allows you to be more specific in your recovery techniques by customizing the approach for handling various exceptions.

Figure 5: Catching an IOException Exception.

Throwable

You can cast a very wide net and catch anything that is Throwable. Whereas this is not necessarily recommended, you could catch all anomalies with code similar to the following code in Listing 6:

public class Anomaly {

   public static void main(String args[]){

      int a = 0;
      int b = 1;
      int c = 0;

      try {
         System.out.println("Divide by Zero");
         c = a/0;

      } catch (Throwable e) {

         System.out.println(e);

      }

      System.out.println("Exit");

   }

}

Listing 6: Catching all things Thrown.

In this case, anything that is thrown will be caught in this try/catch block. There is no granularity when it comes to Errors, Exceptions, RunTimeErrors, and so forth. As far as information reporting goes, note that in this example, you are using the following line inside the catch block itself.

System.out.println(e);

This technique is powerful when your code is in development and/or testing. You can start out by casting a wide net and reporting exceptions and errors at a very high level. However, as the development progresses and testing uncovers various issues, you then can use this information to drill down and handle the various situations that arise. In Figure 6, the printed output actually indicates the specific exception that was encountered—in this case, an ArithmeticException.

Figure 6: Catching all things Thrown.

You also can use a ‘compound’ catch block to catch more than one specific exception, as seen below.

   try {

      ....

   } catch (java.lang.ArithmeticException e) {

      System.out.println(e);

   } catch (java.io.IOException  e) {

      System.out.println(e);

   }

Listing 7 shows an application that combines the two exceptions that I have used in your illustrations, ArithmeticException and FileNotFoundException.

import java.io.*;

public class Anomaly {

   public static void main(String args[]){

      int a = 0;
      int b = 1;
      int c = 0;

      System.out.println("Begin Application");

      try {

         System.out.println("Divide by Zero");

         c = a/0;

         FileInputStream fileStream= new
            FileInputStream("Test.txt");

         DataInputStream fileIn =
            new DataInputStream(fileStream);

         while (fileIn .available() !=0) {
            System.out.println (fileIn .readLine());

         }

         fileIn .close();

      } catch (java.lang.ArithmeticException e) {

         System.out.println(e);

      } catch (java.io.IOException  e) {

         System.out.println(e);

      }

      System.out.println("Exit");

   }

}

Listing 7: Catching more than one individual exception.

The main issue to understand here is that the exceptions will be handled in the order that they are encountered in the catch block. This is important because, if you do something like this, some of the code is considered redundant. For example, in the following code stub, because Exception is listed first, all other exceptions listed in the block (in this case java.io.IOException) are, in fact, redundant. In short, if an IOException is encountered, it will actually be caught by the Exception catch block (because it is indeed an exception) and not even find its way to the IOException catch block.

   try {

      ....

   } catch (Exception e) {

      System.out.println(e);

   } catch (java.io.IOException  e) {

      System.out.println(e);

   }

Conclusion

Designing a strong and effective error handling mechanism is one of the most important considerations when building an application. The object-oriented languages used today provide a solid foundation for exception handling that is built directly into the languages themselves.

This means that the compiler handles much of the anomaly checking that is done. The compiler will in fact determine whether your code does not provide exception handling for certain exceptions. When this happens, you will not be able to compile your code unless you comply with the languages exception-handling model.

There are a lot of subtle issues involved when designing your anomaly handling strategies.

References

About the Author

Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, C#, and .NET as well as various web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry, gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management. Besides The Object-Oriented Thought Process, which is now in its second edition, Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb’s Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories