Error Handling

"Insanity: doing the same thing over and over again and expecting different results."
—Albert Einstein (1879-1955)   

The worst thing about programming has to be having bugs in your code. In most cases the bug is a simple error, which you spend ages looking for only to realize that you commented out the call to the method that you're attempting to debug about half an hour earlier. We will now look at exceptions and assertions and how they are used for handling errors in Java.

Exceptions

In Java, run-time errors are handled using exceptions. Exceptions are objects that are created when your program does something that is deemed to be irregular. When an exception occurs, it is said that an exception has been "thrown." When this happens, the exception must be handled, and it is said that the exception must be "caught." A common exception is the exception ArrayIndexOutOfBoundsException, which is thrown by the Java Virtual Machine when an attempt is made to access an illegal index in an array (where no element exists at the specified index).

So let's make a program that causes this exception to be thrown and then run it and see what happens. If you have not already seen what happens when an exception is thrown, you have either been very careful or very lucky. In any case, the following BrokenArray.java example is designed to cause an exception to be thrown. In it we create an array of type int of length 10. We will then attempt to continually assign values to the elements of the array using a for loop without a termination condition. This means that the value of the counter variable in the for loop, which we will use as the array index value, will continue to increment until its value becomes the value of an invalid index in the array, causing an exception to be thrown. Here is the code for BrokenArray.java:

Code Listing 5-12: BrokenArray.java

start example
public class BrokenArray {     public static void main(String args[])     {           int[] value = new int[10];         for(int i=0;; i++)         {             System.out.println("index = " + i);             value[i] = i;          }     } }
end example

When you compile and run this code, you should get output similar to the following figure.

click to expand
Figure 5-8: The exception is not caught

As you can see, when the example application attempts to access the tenth element of the array value, which does not exist, an ArrayIndexOutOfBoundsException exception was thrown and the application was terminated. So how do we handle this sort of thing? We need to "catch" the exceptions that are "thrown" by the virtual machine.

Using try/catch and finally

Now that we have seen an exception being thrown by the JVM (Java Virtual Machine), let's make a modification to the previous array example so we can catch the exception. We catch the exception by using a try/ catch block, which first tries to execute a section of code and, if an exception is thrown within the try statement, the interpreter then checks the catch statements to see if the exception has been caught. Let's look at the modified code listing for the previous example, where we now catch the ArrayIndexOutOfBounds exception.

Code Listing 5-13: BrokenArrayHandled.java

start example
public class BrokenArrayHandled {     public static void main(String args[])     {           int[] value = new int[10];               try         {            for(int i=0;; i++)            {               System.out.println("index = " + i);               value[i] = i;             }        }        catch(ArrayIndexOutOfBoundsException e)        {            System.out.println("Caught: "+e);        }     } }
end example

When we execute the example now with the try/catch block in it, we can expect the following output.

click to expand
Figure 5-9: The exception is now caught by the try/catch block

So in this example, we placed our for loop within the try block, meaning that if any exceptions are thrown within this block, they will be caught in the catch block, provided that the catch block actually handles the exception. Note that it is also possible to have multiple catch blocks in case it is possible for more than one type of exception to be thrown within the try block. For example:

try {   } catch(Exception1 e) {   } catch(Exception2 e) {   }

When an exception is thrown, it is then passed into the catch block as a parameter. The base class of all exceptions is the java.lang.Exception (readily available) class to which all other exceptions are derived.

catch(Exception e) {     System.out.println("Exception caught: "+ e); }

In this code, when an exception is thrown, the Exception object referenced by "e" will then contain information about the exception, and calling the toString method of the Exception class (or by simply printing the object, which will call its toString method automatically) will give more detailed information about the exception and is a good technique for adding to catch blocks that are not expected to be reached.

As all exceptions are derived from the Exception class, it is sometimes useful when a number of classes can be thrown to just have one catch block, catching an exception of type Exception as we have just seen (all types of exceptions will be caught by this catch block). Note the following example:

int myArray[] = null;   try {     myArray[7] = 77; } catch(Exception e1) {     System.out.println("Caught 1: "+ e1); } catch(ArrayIndexOutOfBoundsException e2) {     System.out.println("Caught 2: "+ e2); }

This code will not compile because the first catch block will catch any exceptions, including the ArrayIndexOutOfBoundsException that is derived from Exception. However, if we swap these catch blocks around, we can first attempt to catch the more specific ArrayIndexOutOfBoundsException and then attempt to catch any other types of exceptions that may have been thrown. The following code will now compile:

try {     myArray[7] = 77; } catch(ArrayIndexOutOfBoundsException e2) {     System.out.println("Caught 2: "+ e2); } catch(Exception e1) {     System.out.println("Caught 1: "+ e1); }

An important distinction that you need to be aware of is that exceptions that are derived from the RuntimeException exception (which is itself a direct subclass of the Exception class) do not need to be caught if "declared to be thrown" by a given method. We will look at throwing exceptions in a moment.

For example, the ArrayIndexOutOfBoundsException exception is in fact derived from the RuntimeException class and does not need to be caught or "declared to be thrown" (we will see about this in a moment also).

When catching an exception, a very useful method that belongs to the Exception object passed to the catch block is the printStackTrace method, which prints out a back-trace of the error that can indicate the paths through the code where the exception came from. This obviously helps the debugging process. For example, we could use the following code in our catch block to print the stack trace:

catch(Exception e) {     e.printStackTrace(); }

If this code is invoked, it will print out information on the classes, methods, and error-causing lines in those methods, tracing the error through its invocation path, which is a great help for debugging when you can see the line of code where the error occurred.

Using the finally Block

There is also the finally block that we can add after the catch block(s). Regardless of whether the try block throws an exception or not, the finally block is always executed at the end, even if a return statement is present in either of the try or catch blocks.

A good example of a use for the finally block would be if you were to open and manipulate a file within the try block. It would then be possible for the try block to throw an exception at any time and miss the rest of the code within the try block, meaning the file could be left open. So in this case it would be a good idea to close the file within a finally block. Here is the pseudocode for this:

try {     // open and manipulate the file } catch(Exception e) {     // print an error message to the user } finally {     // close the file handle and perform any other cleanup     // operations }

Another thing to note regards the use of the keyword return where the finally block is involved. If we say that in the try block there is a scenario that can lead to a return being made, where the method we are in is exited, then in this case the method will not exit right away. Instead, before the return statement is executed, the finally block is first invoked and then the method returns. This is also the same for making a return call in a catch block with the finally block.

Another possible avenue to be aware of, as we are being ultra picky, is if you were to have not only a return call in the try block, but also a return call in the finally block. In this case the return call in the try block is never executed because when it is reached, the finally block is first executed and then the method returns from there before it is able to return to the original return statement within the try block.

Throwing Exceptions

Another useful piece of information is that you can throw an exception back from a method so that the method it was called from can handle it. Let's look at another modified version of the BrokenArray example where we created a new static method to print the array. The full code listing for this example can be seen here:

Code Listing 5-14: BrokenArrayThrow.java

start example
public class BrokenArrayThrow {        public static void printArray() throws         ArrayIndexOutOfBoundsException     {         int[] value = new int[10];              for(int i=0;; i++)         {             System.out.println("index = " + i);             value[i] = i;          }     }         public static void main(String args[])     {                     try         {             printArray();         }         catch(ArrayIndexOutOfBoundsException e)         {             System.out.println("Caught: "+e);         }     } }
end example

As you can see from the code, we have appended throws ArrayIndexOutOfBoundsException to the declaration of our new static method printArray, meaning that if that exception is thrown within the method, it will throw it back to the method that called it. In this case it is the main method, where we handle the exception as we did before.

Throwing Your Own Exceptions

As well as catching the standard Java exceptions, you can also make and throw your own exceptions. Keep in mind, though, that throwing exceptions has many overheads and is best kept to a minimum.

First we need to create our exception class called MyException, which will extend the Exception class that is part of the java.lang package. The full source listing for the MyException class can be seen here:

Code Listing 5-15: MyException.java

start example
public class MyException extends Exception {     public MyException(String theProblem)     {        super(theProblem);     } }
end example

As you can see, all we do here is create a public class called MyException, which extends the Exception class. Then we create a constructor, which accepts a string as a parameter. We then pass this parameter to the constructor of the super class (Exception).

Next we need to create a small program to test our exception by throwing it using the throw keyword, and then we will attempt to catch it. The complete listing for this application can be seen here:

Code Listing 5-16: TestApp.java

start example
public class TestApp {     public static void main(String args[])     {          try        {           MyException myException = new MyException("My Error                Message");           throw myException;        }        catch(MyException e)        {           System.out.println("Caught: "+e);        }     } }
end example

When we run the code, we see the following output in the console window:

click to expand
Figure 5-10: Catching our own exception

So within the try block, we first create an instance of our exception class using the following line of code.

MyException myException = new MyException("My Error Message");

Once we have that, we can then "throw" the exception using the throw keyword, as can be seen in the following line of code.

throw myException;

After it is thrown, the execution will then go to the catch statements and look for one that can handle a MyException exception. So we declare our catch block as follows:

catch(MyException e) {     System.out.println("Caught: "+e); }

This means that it will catch a MyException exception and reference it with the e object.

Note 

When creating your own exception, the printStackTrace method will work similarly with your exception objects as with the standard exceptions, as we discussed earlier.

Errors

Errors are similar to exceptions; however, you should not attempt to catch them. All errors are derived from the java.lang.Error class. The following table shows three of the most common types of error classes:

Error

When it occurs:

LinkageError

A LinkageError is caused by serious problems occurring within your application, such as trying to create an instance of a class that does not exist. It is pretty much impossible to recover from a LinkageError being thrown.

VirtualMachineError

As with the LinkageError, a VirtualMachineError is very serious and occurs in such events as running out of memory and resources.

ThreadDeath

A ThreadDeath error is the least important and is thrown on the termination of an executing thread, whether intentional or accidental.

It is possible to attempt to catch these errors, but there is really little point, as it will be very difficult if not impossible to recover from them. Your best bet is to read what the error was and go back to the code to try and work out what the problem was.

Assertions

Assertions are new to Java 1.4 and are an excellent tool to assist you in debugging your application and applet games. Assertions are simply a way to test situations where you would normally make assumptions as to the values of variables. For example, you could test that an age is not a negative number.

Let's look at a very simple example where we use the assert keyword to test if a Boolean variable called testValue is true or not.

Here is the complete code listing for this example:

Code Listing 5-17: TestApp.java

start example
public class SimpleAssert {     public static void main(String args[])     {         boolean testValue = true;               assert testValue;               testValue = false;               assert testValue;     } }
end example

As the assert keyword is new to the Java language since the 1.4 release of the SDK, we need to compile the code using the -source parameter, where we specify 1.4. Here is the complete command we use to compile the code:

javac -source 1.4 SimpleAssert.java

Then when we execute the application, we need to enable assertions by using either the -enableassertions parameter or the -ea parameter. The complete command for executing the example is as follows:

java -enableassertions SimpleAssert

So when we run the application, we should expect to see the following output:

click to expand
Figure 5-11: Simple assertion

As you can see from the screen shot, the java.lang.AssertionError error was thrown on line 11, which happens to be the line where we assert the testValue variable when it is false.

So an assertion will only throw an error if the test case turns out to be false. Let's look at some situations where using assertions is useful.

Assertions in Control Flow

Assertions can be used to detect if the execution is reaching areas that it should not be. For example, let's look at this simple method:

public int returnNumber(int myNumber) {     if(myNumber > 0)     {         return myNumber;     }     else     {         return 0;     }       assert false; }

In the example above, you can see that the assert should never be reached, as the method returns from both the if statement and the else statement.

Think of assert as a sort of security blanket for you. In the previous example the execution would never reach the assert, but in more complex code there would be no reason not to put one there, just in case.

Assertions in Internal Invariants

Impressive section heading, eh? It is really nothing complicated. Basically, it is about using assertions to test assumptions, which are made within the else statements of if/else blocks and also within the default case of switch statements.

Let's say you have, for example, an if statement to test a condition like the following:

if(i > 0) {     // true statement code here… }

Then if you append an else block to the end, you are making the assumption that the i variable will be less than or equal to zero.

if(i > 0) {     // true statement code here… } else {     // you assume that 'i' is equal or less than zero here }

So, you could place an assertion in the else statement to ensure that i is equal to or less than zero. Our statement would then look as follows.

if(i > 0) {     // true statement code here… } else {     assert i <= 0; }

Another useful part of assertions is that you can also store a numeric value within the assertion. If we use the previous example and store the i variable, the assert line would then look as follows:

assert i <= 0 : i;

Therefore, if the AssertionError is thrown, the value of i will also be printed. Also, note that where we add the i after the colon (:), we can also add other data types, such as string values for more informative error reporting.

Other Useful Notes

By default, assertions are turned off. If you remember in the previous assertion example that we compiled and ran, you were required to add the -enableassertions parameter so that the assertions would be used. If this parameter is omitted from the command line when running your program, the assertions will not be enabled. When assertions are not enabled, any assertion statements in your code will be ignored, so you need to be careful that you do not actually place any important code in the assertion statement. Here is an example of some bad assertion code:

assert i++ > 0;    // bad

If assertions were switched on, the assertion would test if i were greater than zero and increment i's value by one. Of course, if assertions were off, the line would not be executed; therefore, i would not be incremented, which could have other implications within your program.

The best way around this is to create a Boolean variable to store the result and test the Boolean variable with the assertion. Here is a better way to perform the previous assertion:

boolean  result = i++ > 0; assert result;    // good



Java 1.4 Game Programming
Java 1.4 Game Programming (Wordware Game and Graphics Library)
ISBN: 1556229631
EAN: 2147483647
Year: 2003
Pages: 237

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net