Tuesday, November 11, 2008

Using try and catch

Using try and catch

Although the default exception handler provided by the Java run-time system is useful for debugging, you will usually want to handle an exception yourself. Doing so provides two benefits. First, it allows you to fix the error. Second, it prevents the program from automatically terminating. Most users would be confused (to say the least) if your program stopped running and printed a stack trace whenever an error

occurred! Fortunately, it is quite easy to prevent this.

 

To guard against and handle a run-time error, simply enclose the code that you want to monitor inside a try block. Immediately following the try block, include a catch clause that specifies the exception type that you wish to catch. To illustrate how easily this can be done, the following program includes a try block and a catch clause which processes the ArithmeticException generated by the division-by-zero error:

 

class Exc2 {

public static void main(String args[]) {

int d, a;

try { // monitor a block of code.

d = 0;

a = 42 / d;

System.out.println("This will not be printed.");

} catch (ArithmeticException e) { // catch divide-by-zero error

System.out.println("Division by zero.");

}

System.out.println("After catch statement.");

}

}

 

This program generates the following output:

 

Division by zero.

After catch statement.

 

Notice that the call to println( ) inside the try block is never executed. Once an exception is thrown, program control transfers out of the try block into the catch block.

 

Put differently, catch is not "called," so execution never "returns" to the try block from a catch. Thus, the line "This will not be printed." is not displayed. Once the catch statement has executed, program control continues with the next line in the program following the entire try/catch mechanism.

 

A try and its catch statement form a unit. The scope of the catch clause is restricted to those statements specified by the immediately preceding try statement. A catch statement cannot catch an exception thrown by another try statement (except in the case of nested try statements, described shortly). The statements that are protected by try must be surrounded by curly braces. (That is, they must be within a block.) You cannot use try on a single statement.

 

The goal of most well-constructed catch clauses should be to resolve the exceptional condition and then continue on as if the error had never happened.

 

For example, in the next program each iteration of the for loop obtains two random integers. Those two integers are divided by each other, and the result is used to divide the value 12345. The final result is put into a. If either division operation causes a divide-by-zero error, it is caught, the value of a is set to zero, and the program continues.

 

// Handle an exception and move on.

import java.util.Random;

class HandleError {

public static void main(String args[]) {

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

Random r = new Random();

for(int i=0; i<32000; i++) {

try {

b = r.nextInt();

c = r.nextInt();

a = 12345 / (b/c);

} catch (ArithmeticException e) {

System.out.println("Division by zero.");

a = 0; // set a to zero and continue

}

System.out.println("a: " + a);

}

}

}

Exception Handling

Exception Handling:

 

This Post examines Java's exception-handling mechanism. An exception is an abnormal condition that arises in a code sequence at run time. In other words, an exception is a run-time error. In computer languages that do not support exception handling, errors must be checked and handled manually—typically through the use of error codes, and so on. This approach is as cumbersome as it is troublesome.

 

Java's exception handling avoids these problems and, in the process, brings run-time error management into the object-oriented world. For the most part, exception handling has not changed since the original version of Java. However, Java 2, version 1.4 has added a new subsystem called the chained exception facility. This feature is described near the end of this Post.

 

Exception-Handling Fundamentals

A Java exception is an object that describes an exceptional (that is, error) condition that has occurred in a piece of code. When an exceptional condition arises, an object representing that exception is created and thrown in the method that caused the error.

 

That method may choose to handle the exception itself, or pass it on. Either way, at some point, the exception is caught and processed. Exceptions can be generated by the Java run-time system, or they can be manually generated by your code. Exceptions thrown by Java relate to fundamental errors that violate the rules of the Java language or the constraints of the Java execution environment. Manually generated exceptions are typically used to report some error condition to the caller of a method.

 

Java exception handling is managed via five keywords: try, catch, throw, throws, and finally. Briefly, here is how they work. Program statements that you want to monitor for exceptions are contained within a try block. If an exception occurs within the try block, it is thrown. Your code can catch this exception (using catch) and handle it in some rational manner. System-generated exceptions are automatically thrown by the Java run-time system. To manually throw an exception, use the keyword throw.

 

Any exception that is thrown out of a method must be specified as such by a throws clause. Any code that absolutely must be executed before a method returns is put in a finally block.

 

This is the general form of an exception-handling block:

 

try {

// block of code to monitor for errors

}

catch (ExceptionType1 exOb) {

// exception handler for ExceptionType1

}

catch (ExceptionType2 exOb) {

// exception handler for ExceptionType2

}

// ...

finally {

// block of code to be executed before try block ends

}

 

Here, ExceptionType is the type of exception that has occurred. The remainder of this following post describes how to apply this framework.

 

Exception Types

All exception types are subclasses of the built-in class Throwable. Thus, Throwable is at the top of the exception class hierarchy. Immediately below Throwable are two subclasses that partition exceptions into two distinct branches. One branch is headed by Exception. This class is used for exceptional conditions that user programs should catch. This is also the class that you will subclass to create your own custom exception types. There is an important subclass of Exception, called RuntimeException.

 

Exceptions of this type are automatically defined for the programs that you write and include things such as division by zero and invalid array indexing.

The other branch is topped by Error, which defines exceptions that are not expected to be caught under normal circumstances by your program.

 

Exceptions of type Error are used by the Java run-time system to indicate errors having to do with the run-time environment, itself. Stack overflow is an example of such an error.

 

This post will not be dealing with exceptions of type Error, because these are typically created in response to catastrophic failures that cannot usually be handled by your program.

 

Uncaught Exceptions

Before you learn how to handle exceptions in your program, it is useful to see what happens when you don't handle them. This small program includes an expression that intentionally causes a divide-by-zero error.

 

class Exc0 {

public static void main(String args[]) {

int d = 0;

int a = 42 / d;

}

}

 

When the Java run-time system detects the attempt to divide by zero, it constructs a new exception object and then throws this exception. This causes the execution of Exc0 to stop, because once an exception has been thrown, it must be caught by an exception handler and dealt with immediately.

 

In this example, we haven't supplied any exception handlers of our own, so the exception is caught by the default handler provided by the Java run-time system. Any exception that is not caught by your program will ultimately be processed by the default handler. The default handler displays a string describing the exception, prints a stack trace from the point at which the exception occurred, and terminates the program.

 

Here is the output generated when this example is executed.

 

java.lang.ArithmeticException: / by zero

at Exc0.main(Exc0.java:4)

 

Notice how the class name, Exc0; the method name, main; the filename, Exc0.java; and the line number, 4, are all included in the simple stack trace. Also, notice that the type of the exception thrown is a subclass of Exception called ArithmeticException, which more specifically describes what type of error happened. As discussed later in this Post, Java supplies several built-in exception types that match the various sorts of run-time errors that can be generated.

 

The stack trace will always show the sequence of method invocations that led up to the error.

 

For example, here is another version of the preceding program that introduces the same error but in a method separate from main( ):

 

class Exc1 {

static void subroutine() {

int d = 0;

int a = 10 / d;

}

public static void main(String args[]) {

Exc1.subroutine();

}

}

 

The resulting stack trace from the default exception handler shows how the entire call stack is displayed:

 

java.lang.ArithmeticException: / by zero

at Exc1.subroutine(Exc1.java:4)

at Exc1.main(Exc1.java:7)

 

As you can see, the bottom of the stack is main's line 7, which is the call to subroutine( ), which caused the exception at line 4. The call stack is quite useful for debugging, because it pinpoints the precise sequence of steps that led to the error.

Variables in Interfaces

Variables in Interfaces:

 

You can use interfaces to import shared constants into multiple classes by simply declaring an interface that contains variables which are initialized to the desired values. When you include that interface in a class (that is, when you "implement" the interface), all of those variable names will be in scope as constants.

 

This is similar to using a header file in C/C++ to create a large number of #defined constants or const declarations. If an interface contains no methods, then any class that includes such an interface doesn't actually implement anything. It is as if that class were importing the constant variables into the class name space as final variables.

 

The next example uses this technique to implement an automated "decision maker":

 

import java.util.Random;

interface SharedConstants {

int NO = 0;

int YES = 1;

int MAYBE = 2;

int LATER = 3;

int SOON = 4;

int NEVER = 5;

}

class Question implements SharedConstants {

Random rand = new Random();

int ask() {

int prob = (int) (100 * rand.nextDouble());

if (prob < 30)

return NO; // 30%

else if (prob < 60)

return YES; // 30%

else if (prob < 75)

return LATER; // 15%

else if (prob < 98)

return SOON; // 13%

else

return NEVER; // 2%

}

}

class AskMe implements SharedConstants {

static void answer(int result) {

switch(result) {

case NO:

System.out.println("No");

break;

case YES:

System.out.println("Yes");

break;

case MAYBE:

System.out.println("Maybe");

break;

case LATER:

System.out.println("Later");

break;

case SOON:

System.out.println("Soon");

break;

case NEVER:

System.out.println("Never");

break;

}

}

public static void main(String args[]) {

Question q = new Question();

answer(q.ask());

answer(q.ask());

answer(q.ask());

answer(q.ask());

}

}

 

Notice that this program makes use of one of Java's standard classes: Random. This class provides pseudorandom numbers. It contains several methods which allow you to obtain random numbers in the form required by your program. In this example, the method nextDouble( ) is used. It returns random numbers in the range 0.0 to 1.0.

 

In this sample program, the two classes, Question and AskMe, both implement the SharedConstants interface where NO, YES, MAYBE, SOON, LATER, and NEVER are defined. Inside each class, the code refers to these constants as if each class had defined or inherited them directly. Here is the output of a sample run of this program. Note that the results are different each time it is run.

 

Later

Soon

No

Yes

 

 

Interfaces Can Be Extended:

 

One interface can inherit another by use of the keyword extends. The syntax is the same as for inheriting classes. When a class implements an interface that inherits another interface, it must provide implementations for all methods defined within the interface inheritance chain. Following is an example:

 

// One interface can extend another.

interface A {

void meth1();

void meth2();

}

// B now includes meth1() and meth2() -- it adds meth3().

interface B extends A {

void meth3();

}

// This class must implement all of A and B

class MyClass implements B {

public void meth1() {

System.out.println("Implement meth1().");

}

public void meth2() {

System.out.println("Implement meth2().");

}

public void meth3() {

System.out.println("Implement meth3().");

}

}

class IFExtend {

public static void main(String arg[]) {

MyClass ob = new MyClass();

ob.meth1();

ob.meth2();

ob.meth3();

}

}

 

As an experiment you might want to try removing the implementation for meth1( ) in MyClass. This will cause a compile-time error. As stated earlier, any class that implements an interface must implement all methods defined by that interface, including any that are inherited from other interfaces.

 

Although the examples we've included in this book do not make frequent use of packages or interfaces, both of these tools are an important part of the Java programming environment. Virtually all real programs and applets that you write in Java will be contained within packages. A number will probably implement interfaces as well. It is important, therefore, that you be comfortable with their usage.

Applying Interfaces

Applying Interfaces:

 

To understand the power of interfaces, let's look at a more practical example. In earlier Posts you developed a class called Stack that implemented a simple fixed-size stack. However, there are many ways to implement a stack. For example, the stack can be of a fixed size or it can be "growable." The stack can also be held in an array, a linked list, a binary tree, and so on. No matter how the stack is implemented, the interface to the stack remains the same. That is, the methods push( ) and pop( ) define the interface to the stack independently of the details of the implementation. Because the interface to a stack is separate from its implementation, it is easy to define a stack interface, leaving it to each implementation to define the specifics.

 

Let's look at two examples:

 

First, here is the interface that defines an integer stack. Put this in a file called IntStack.java. This interface will be used by both stack implementations.

 

// Define an integer stack interface.

interface IntStack {

void push(int item); // store an item

int pop(); // retrieve an item

}

 

The following program creates a class called FixedStack that implements a fixed-length version of an integer stack:

 

// An implementation of IntStack that uses fixed storage.

class FixedStack implements IntStack {

private int stck[];

private int tos;

// allocate and initialize stack

FixedStack(int size) {

stck = new int[size];

tos = -1;

}

// Push an item onto the stack

public void push(int item) {

if(tos==stck.length-1) // use length member

System.out.println("Stack is full.");

else

stck[++tos] = item;

}

// Pop an item from the stack

public int pop() {

if(tos < 0) {

System.out.println("Stack underflow.");

return 0;

}

else

return stck[tos--];

}

}

class IFTest {

public static void main(String args[]) {

FixedStack mystack1 = new FixedStack(5);

FixedStack mystack2 = new FixedStack(8);

// push some numbers onto the stack

for(int i=0; i<5; i++) mystack1.push(i);

for(int i=0; i<8; i++) mystack2.push(i);

// pop those numbers off the stack

System.out.println("Stack in mystack1:");

for(int i=0; i<5; i++)

System.out.println(mystack1.pop());

System.out.println("Stack in mystack2:");

for(int i=0; i<8; i++)

System.out.println(mystack2.pop());

}

}

 

Following is another implementation of IntStack that creates a dynamic stack by use of the same interface definition. In this implementation, each stack is constructed with an initial length. If this initial length is exceeded, then the stack is increased in size. Each time more room is needed, the size of the stack is doubled.

 

// Implement a "growable" stack.

class DynStack implements IntStack {

private int stck[];

private int tos;

// allocate and initialize stack

DynStack(int size) {

stck = new int[size];

tos = -1;

}

// Push an item onto the stack

public void push(int item) {

// if stack is full, allocate a larger stack

if(tos==stck.length-1) {

int temp[] = new int[stck.length * 2]; // double size

for(int i=0; i<stck.length; i++) temp[i] = stck[i];

stck = temp;

stck[++tos] = item;

}

else

stck[++tos] = item;

}

// Pop an item from the stack

public int pop() {

if(tos < 0) {

System.out.println("Stack underflow.");

return 0;

}

else

return stck[tos--];

}

}

class IFTest2 {

public static void main(String args[]) {

DynStack mystack1 = new DynStack(5);

DynStack mystack2 = new DynStack(8);

// these loops cause each stack to grow

for(int i=0; i<12; i++) mystack1.push(i);

for(int i=0; i<20; i++) mystack2.push(i);

System.out.println("Stack in mystack1:");

for(int i=0; i<12; i++)

System.out.println(mystack1.pop());

System.out.println("Stack in mystack2:");

for(int i=0; i<20; i++)

System.out.println(mystack2.pop());

}

}

 

The following class uses both the FixedStack and DynStack implementations. It does so through an interface reference. This means that calls to push( ) and pop( ) are resolved at run time rather than at compile time.

 

/* Create an interface variable and

access stacks through it.

*/

class IFTest3 {

public static void main(String args[]) {

IntStack mystack; // create an interface reference variable

DynStack ds = new DynStack(5);

FixedStack fs = new FixedStack(8);

mystack = ds; // load dynamic stack

// push some numbers onto the stack

for(int i=0; i<12; i++) mystack.push(i);

mystack = fs; // load fixed stack

for(int i=0; i<8; i++) mystack.push(i);

mystack = ds;

System.out.println("Values in dynamic stack:");

for(int i=0; i<12; i++)

System.out.println(mystack.pop());

mystack = fs;

System.out.println("Values in fixed stack:");

for(int i=0; i<8; i++)

System.out.println(mystack.pop());

}

}

 

In this program, mystack is a reference to the IntStack interface. Thus, when it refers to ds, it uses the versions of push( ) and pop( ) defined by the DynStack implementation.

 

When it refers to fs, it uses the versions of push( ) and pop( ) defined by FixedStack. As explained, these determinations are made at run time. Accessing multiple implementations of an interface through an interface reference variable is the most powerful way that Java achieves run-time polymorphism.