5.6 Exceptions

Exceptions are sort of super-goto which can transfer control not only within a method, but even terminate the current method to find its destination further up the Java stack. Exceptions are used to indicate that something has gone wrong with a program. Usually, an exception indicates that the problem is fairly severe and the operation being attempted should be aborted.

To indicate that a problem has occurred, the program throws an exception. When an exception is thrown, the JVM will try to find an exception handler that can handle the exception at the current location in the current method. This is called the active exception handler. If there is none, the JVM terminates the current method and checks again with the method that called the current method. It repeats this operation until it finds a handler. The handler should clean up the state of the system and correct the error, if possible.

An exception is thrown with the athrow instruction. The athrow instruction takes one operand from the top of the stack: an object that must be a subclass of java/lang/Throwable. Usually, when you create an exception class, it is a subclass of either java/lang/Exception or java/lang/Error, depending on the nature of the error.

This class declaration declares a SnackException, which indicates that some sort of problem has occurred while snacking:

 .class SnackException .super java/lang/Exception     ; Exception is a Throwable ; A constructor which takes a message string .method <init> (Ljava/lang/String;)V aload_0                        ; Initialize the superclass aload_1 invokespecial java/lang/Exception/<init> (Ljava/lang/String;)V return .end method 

A SnackException occurs when the program is having a snack and notices that it can't continue for some reason (for example, there's no more milk). To indicate that you're out of milk, use this code:

 ; I'm out of milk! new SnackException         ; Create a SnackException dup ldc "Out of milk"          ; Initialize it with a message invokespecial SnackException/<init> (Ljava/lang/String;)V athrow                     ; Throw the exception 

When an exception is thrown, control transfers to the most current exception handler. An exception handler is specified in Oolong with a .catch directive:

 .catch class from begin-label to end-label using handler-label 

Here, class is the type of exception to catch, and begin-label and end-label indicate the range of code within the method that is covered by the exception handler. This allows you to use different exception handlers for the same exception in different parts of the method. The handler-label designates which code will be called when the exception occurs.

When an exception occurs and the program is currently between begin-label and end-label, control transfers to handler-label as long as the exception object is an instance of the class or one of its subclasses. If a SnackException is thrown, it can be caught by an exception handler catching SnackExceptions, Exceptions, or Throwables but not by an exception handler catching Errors, because SnackException is not a subclass of Error. Similarly, an exception handler catching SnackExceptions does not catch NullPointerExceptions, because NullPointerException is not a subclass of SnackException.

For example,

 .method nosh()V .catch SnackException from begin to end using        SnackExceptionHandler begin: ; Do stuff which might cause a SnackException getstatic Refrigerator/milkQuantity I    ; Do we have milk? ifgt continue                            ; Drink if milk>0 ; I'm out of milk! new SnackException                  ; Create a SnackException dup ldc "Out of milk"                   ; Initialize it with a message invokespecial SnackException/<init> (Ljava/lang/String;)V athrow                              ; Throw the exception continue: ; We do have milk, so we can drink some end: ; The code here is not covered by the SnackException handler, ; because it is out of range. return SnackExceptionHandler: ;; Do something with the exception. Maybe notify the user ;; to go get some more milk. return .end method 

An exception handler is active whenever the current instruction is between begin-label and end-label. "Between" means that the program counter is after begin-label and before end-label.

When an athrow instruction is executed, the JVM looks to see if there is an exception handler active in the current method that can handle the exception. If there is one, then control transfers to the handler.

The handler code begins with the thrown object on top of the stack. This is the object that was the operand to athrow. The following example shows what happens when you run out of cookies.

 .catch SnackException from begin to end using handler begin:                                         ; Print a message getstatic java/lang/System/out Ljava/io/PrintStream; ldc "At begin" invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V new SnackException                      ; Create a SnackException dup ldc "Out of cookies" invokespecial SnackException/<init> (Ljava/lang/String;)V athrow                                  ; Throw it getstatic java/lang/System/out Ljava/io/PrintStream; ldc "This code doesn't get executed due to the exception" invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V end: return handler: ; The exception is now on top of the stack invokevirtual java/lang/Throwable/getMessage      ()Ljava/lang/String; getstatic java/lang/System/out Ljava/io/PrintStream; swap                                ; Put out below the message invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V return 

When this code is executed, it prints

 At begin Out of cookies 

The code after the athrow is never executed, because control went to the handler instead.

If there is no active exception handler that can handle the exception, then the current method is terminated and control passes back to the calling method right after the invocation. It's as if the method has returned, except that no value is returned, even if one was expected. The JVM continues looking for an exception handler from that point. This can continue indefinitely as long as no exception handler is found. If no exception handler is found anywhere, the exception is finally handled by the ThreadGroup, which usually prints out an error message like this one:

 java.lang.NullPointerException    at Program.bottomOfTheStack(Program.java:18)    at Program.middleOfTheStack(Program.java:14)    at Program.topOfTheStack(Program.java:10)    at Program.main(Program.java:5) 

Here's an example of how an exception can be thrown from one method invocation to another:

 .class BedtimeRoutine .method goToBed()V .catch SnackException from begin to end using handler    ; This call is not covered by the exception handler    invokestatic BedtimeRoutine/putOnPajamas()V    ; SnackException coverage begins here begin:    ; Invoke a function that might throw SnackException    invokestatic BedtimeRoutine/haveMidnightSnack ()V    ; SnackException coverage ends here end:    ; This call is not covered by the exception handler either    invokestatic BedtimeRoutine/brushTeeth()V    return                      ;Successful completion handler:    ;; Handle the exception that occured when trying to have    ;; a snack    return .end method .method static haveMidnightSnack()V getstatic Refrigerator/milkQuantity I   ; Do we have milk? ifgt drink_milk                         ; Drink if got milk ; I'm out of milk! new SnackException                      ; Create a SnackException dup ldc "Out of milk"                       ; Initialize it with a message invokespecial SnackException/<init> (Ljava/lang/String)V athrow                                  ; Throw the exception ; Successfully got milk. Now drink it. drink_milk: getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Drinking milk" invokevirtual java/io/PrintStream/println (Ljava/lang/String;)V return .end method 

Suppose that program calls goToBed, which calls haveMidnightSnack. If there's milk, then everything is fine; you drink the milk, return to the goToBed method, and brush your teeth.

If you discover that you're out of milk in haveMidnightSnack, a SnackException is thrown. There's no handler for it in haveMidnightSnack, so the method is terminated and control returns to goToBed. This method does have a handler for a SnackException. What happens there is not specified, but you might skip the midnight snack and go straight to bed, or you might run down to the store to buy more milk.

5.6.1 Multiple Exception Handlers

When a particular instruction is covered by several exception handlers, the first applicable one within the frame is taken. For example,

 .catch SnackException from begin2 to end2 using handler2 .catch java/lang/Exception from begin1 to end1 using handler1 begin1:    invokestatic BedtimeRoutine/putOnPajamas()V begin2:    ; Invoke a function that might throw SnackException    invokestatic BedtimeRoutine/haveMidnightSnack ()V end2:    ; This call is not covered by the exception handler either    invokestatic BedtimeRoutine/brushTeeth()V end1:    return handler2:    ; The stack contains a SnackException; just return    return handler1:    ; The stack contains a more general Exception; print an    ; error message    invokevirtual java/lang/Throwable/getMessage       ()Ljava/lang/String;    getstatic java/lang/System/out Ljava/io/PrintStream;    swap                        ; Put out below the message    invokevirtual java/io/PrintStream/println       (Ljava/lang/String;)V    return 

There is an exception handler that covers the entire range from begin1 to end1. It handles the very general class java/lang/Exception, which covers exceptions like NullPointerException and ArrayIndexOutOfBoundsException, as well as SnackExceptions and most other application-defined Exceptions. These are handled by the code at handler1, which prints out the message associated with the exception. All exceptions inherit the method getMessage from java/lang/Throwable, which returns a detail message associated with the exception.

The code from begin2 to end2 is covered by two different exception handlers. If a SnackException occurs, then it is handled by the code at handler2. In this code, the snack exception isn't considered important, so the method just returns. If any other kind of Exception occurs, it is handled by the more general exception handler at handler1.

If a SnackException were to occur for some reason from brushTeeth or putOnPajamas, the handler at handler1 would handle the exception, since the other handler is not active while calling those two methods. Only the call to haveMidnightSnack is covered.

It is important to realize that the order of the .catch directives matters. If a SnackException occurs during haveMidnightSnack, the handler at handler2 is used because its .catch directive comes first not because SnackException is more specific than Exception. Suppose you reversed the .catch directives:

 .catch java/lang/Exception from begin1 to end1 using handler1 .catch SnackException from begin2 to end2 using handler2 

In this case, the SnackException handler is totally hidden by the java/lang/Exception handler. The code at handler2 will never be called, because any SnackException thrown will be handled by the first exception handler.

As a general rule, write your code so that the more specific exception handlers come before the more general ones.

Exercise 5.6

Add two subclasses of SnackException called OutOfMilkException and OutOfCookiesException. Give them constructors that have a default message. How would you modify the code to handle an OutOfMilkException differently from other SnackExceptions?

Exercise 5.7

In Java, there can be several catches for a try:

 try {    foo(); } catch(Exception e1) {    System.out.println(e1.getMessage()); } catch(NullPointerException e2) {    System.out.println(e2.getMessage()); } 

The Java compiler generates an error message for this code. Why do you think this is?

5.6.2 .throws Directive

You can alert somebody using your method to what exceptions the method might throw with the .throws directive:

 .method lateNightSnack()V .throws OutofMilkException .throws OutofCookiesException 

The .throws directives are optional. They're used to support a Java language requirement that a method can throw only exceptions that are explicitly listed. The following Java method

 void eveningActivities() {    watchTV();    lateNightSnack();    goToBed(); } 

will evoke an error from the Java compiler stating that the OutOfCookiesException and OutOfMilkException exceptions must either be caught or listed in the throws clause of the method declaration:

 void eveningActivities() throws OutOfMilk, OutOfCookies 

If you don't list the possible exceptions in a .throws clause, then the Java compiler won't know that your method might throw an exception, and somebody using the eveningActivities method would be surprised if the method terminates abnormally. Writing .throws directives is more work for you, but it makes life easier on the next programmer who uses the code.

5.6.3 Other Ways to Throw Exceptions

Besides athrow, exceptions can also be thrown by other untoward events happening in the virtual machine. For example, this code tries to divide 1 by 0:

 iconst_1 iconst_0 idiv 

It results in an ArithmeticException when you execute the program. Similarly, attempting to call a method on null will cause an exception:

 aload_null invokevirtual java/lang/Object/toString ()Ljava/lang/String; 

This will cause a NullPointerException.

These machine-signaled exceptions can be caught with a .catch directive, just like any other exception.

Exercise 5.8

Another kind of machine-generated exception is the java/lang/ArrayIndexOutOfBoundsException, which occurs when you try to use an element of an array that does not exist. Use this fact to write a program that lists all its arguments without using any if branches. (You may use a goto.).



Programming for the Java Virtual Machine
Programming for the Javaв„ў Virtual Machine
ISBN: 0201309726
EAN: 2147483647
Year: 1998
Pages: 158
Authors: Joshua Engel

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