Principle 10.4.3. EFFECTIVE DESIGN: Using an Exception.
Unless your program’s handling of an exception is significantly different from Java’s default handling of it, the program should just rely on the default.
avgFirstN()
example, the typical way of handling this error in Java would be to throw an exception in the avgFirstN() method and catch it in the calling method. Of course, the calling method could be in the same object or it could belong to some other object. In the latter case, the detection of the error is separated from its handling. This division of labor opens up a wide range of possibilities. For example, a program could dedicate a single object to serve as the handler for all its exceptions. The object would be sort of like the program’s fire department.try/throw/catch
mechanism, let’s revisit the CalcAvgTest
program. The version shown in Listing 10.4.2 mimics the way Java’s default exception handler works. If the avgFirstN()
method is called with an argument that is zero or negative, an IllegalArgumentException
is thrown. The exception is caught by the catch
clause in the CalcAvgTest.main()
method.try {
CalcAverage ca = new CalcAverage();
System.out.println( "AVG + " + ca.avgFirstN(0));
}
catch (IllegalArgumentException e) { // Exception Handler
System.out.println(e.getMessage());
e.printStackTrace();
System.exit(0);
}
CalcAverage.avgFirstN()
method has a zero or negative argument, it will throw
an exception:if (N <= 0)
throw new IllegalArgumentException("ERROR: Illegal argument");
throw
statement. It creates a new IllegalArgumentException
object and passes it a message that describes the error. This message becomes part of the exception object. It can be retrieved using the getMessage()
method, which is inherited from the Throwable
class (Figure 10.3.1).throw
statement is executed, the JVM interrupts the normal execution of the program and searches for an exception handler. We will describe the details of this search shortly. In this case, the exception handler is the catch
clause contained in the CalcAvgTest.main()
method:catch (IllegalArgumentException e) { // Exception Handler
System.out.println(e.getMessage());
e.printStackTrace();
System.exit(0);
}
IllegalArgumentException
is thrown, the statements within this catch
clause are executed. The first statement uses the getMessage()
method to print a copy of the error message. The second statement uses the printStackTrace()
method, which is defined in Throwable
and inherited by all Exception
s, to print a trace of the method calls leading up to the exception. The last statement causes the program to terminate.ERROR: Can't average 0 elements
java.lang.IllegalArgumentException: ERROR: Illegal argument
at java.lang.Throwable.fillInStackTrace(Native Method)
at java.lang.Throwable.<init>(Throwable.java:94)
at java.lang.Exception.<init>(Exception.java:42)
at java.lang.RuntimeException.<init>
(RuntimeException.java:47)
at java.lang.IllegalArgumentException.<init>
(IllegalArgumentException.java:43)
at CalcAverage.avgFirstN(Compiled Code)
at CalcAvgTest.main(CalcAvgTest.java:5)
catch
clause is associated with a try
block. The handling of exceptions in Java takes place in two parts: First, we try to execute some statements, which may or may not lead to an exception. These are the statements contained within the try
clause:try {
CalcAverage ca = new CalcAverage();
System.out.println( "AVG + " + ca.avgFirstN(0));
}
catch
clauses to handle particular types of exceptions. In this case, we are only handling IllegalArgumentException
s.try
block. The “fire department” in this case is the code contained in the catch
clause that immediately follows the try block. This is the exception handler for this particular exception. There’s something like a game of catch going on here: Some method within the try block throws an Exception
object, which is caught and handled by the catch block located in some other object (Figure 10.4.4).CalcAvgTest
example, an important difference between Java’s exception handling and more traditional approaches is that error handling can be separated from the normal flow of execution within a program. The CalcAverage.avgFirstN()
method still checks for the error and it still throws
IllegalArgumentException
if N does not satisfy the method’s precondition. But it does not contain code for handling the exception. The exception-handling code is located in the CalcAvgTest
class.CalcAvgTest
program creates a clear separation between the normal algorithm and the exception-handling code. One advantage of this design is that the normal algorithm is uncluttered by error-handling code and, therefore, easier to read.CalcAvgTest.main()
, one exception handler can be used to handle other errors of that type. For example, this catch clause could handle allIllegalArgumentException
s that get thrown in the program. Its use of printStackTrace()
will identify exactly where the exception occurred. In fact, because a Java application starts in the main()
method, encapsulating all of a program’s executable statements within a single try
block in the main()
method will effectively handle all the exceptions that occur within a program.try
followed by a block of code enclosed within curly braces. A catch clause or catch block consists of the keyword catch
, followed by a parameter declaration that identifies the type of Exception
being caught, followed by a collection of statements enclosed within curly braces. These are the statements that handle the exception by taking appropriate action.try/catch
statement is summarized in Listing 10.4.7.catch
block that matches the particular kind of exception that was thrown. Exceptions are thrown by using the throw
statement, which takes the following general form:throw new ExceptionClassName(OptionalMessageString);
throw
is followed by the instantiation of an object of the ExceptionClassName
class. This is done the same way we instantiate any object in Java: by using the new
operator and invoking one of the exception’s constructor methods. Some of the constructors take an OptionalMessageString
, which is the message that gets returned by the exception’s getMessage()
method.catch
block has the following general form:catch (ExceptionClassName ParameterName) {
// Exception handling statements
}
catch
block is very much like a method definition. It contains a parameter, which specifies the class of exception that is handled by that block. The ParameterName can be any valid identifier, but it is customary to use e
as the catch
block parameter. The parameter’s scope is limited to the catch block, and it is used to refer to the caught exception.ArithmeticException
is thrown, it will match both an ArithmeticException
parameter and an Exception
parameter, because ArithmeticException
is a subclass of Exception
.catch
clauses associated with a given try
block, and the order with which they are arranged is important. A thrown exception will be caught by the first catch
clause it matches. Therefore, catch
clauses should be arranged in order from most specific to most general (See the exception hierarchy in Figure 10.3.1). If a more general catch clause precedes a more specific one, it will prevent the more specific one from executing. In effect, the more specific clause will be hidden by the more general one. You might as well just not have the more specific clause at all.ArithmeticException
is thrown in the following try/catch
statement:try {
// Suppose an ArithmeticException is thrown here} catch (ArithmeticException e) {
System.out.println("ERROR: " + e.getMessage() );
e.printStackTrace();
System.exit(1);} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage() );
}
ArithmeticException
block. On the other hand, if some other kind of exception is raised, it will be caught by the second catch clause. The Exception
class will match any exception that is thrown. Therefore, it should always occur last in a sequence of catch
clauses.Exception
clause should always be the last in the sequence.try/catch/finally
Statement
throw
statement is used to throw both checked exceptions and unchecked exceptions, where unchecked exceptions are those belonging to RuntimeException
or its subclasses. Unchecked exceptions need not be caught by the program.throw
statement must be contained within the dynamic scope of a try block, and the type of Exception
thrown must match at least one of the try block’s catch clauses. Or the throw
statement must be contained within a method or constructor that has a throws
clause for the type of thrown Exception
.catch
clause in CalcAvgTest.main()
when an exception is thrown in avgFirstN()
? Also, doesn’t the latest version of avgFirstN()
(Listing 10.4.2) violate the restriction that a throw
statement must occur within a try block?throw
statement must fall within the dynamic scope of an enclosing try block. Let’s see what this means.CalcAverage
(Listing 10.4.2), the avgFirstN()
method is called from within the try block located in CalcAvgTest.main()
. Thus, it falls within the dynamic scope of that try block.MyClass
(Listing 10.4.11). The variable X
occurs within the (static) scope of method1()
, and the variable Y
occurs within the (static) scope of method2()
.MyClass
definition, the System.out.println()
statements occur within the static scope of method1()
and method2()
, respectively. In general, static scoping refers to where a variable is declared or where a statement is located. Static scoping can be completely determined by just reading the program.Math.random()
. Suppose that when random()
is executed it returns the value 0.99. In that case, main()
will call method2()
, which will call System.out.println()
, which will print “Hello2.” In that case, the statement System.out.println("Hello" + Y
) has the following dynamic scope:main()
method2()
System.out.println("Hello" + Y);
method2()
, which is within the (dynamic) scope of main()
. On the other hand, if the result of random()
had been 0.10, that particular println()
statement wouldn’t have been executed at all. Thus, to determine the dynamic scope of a particular statement, you must trace the program’s execution. In fact, this is what the printStackTrace()
method does. It prints a trace of a statement’s dynamic scope.CalcAvgTest()
example (Listing 10.4.2), Java would search backward to the CalcAvgTest.main()
method, which is where avgFirstN()
was called, and it would find the catch
clause there for handling IllegalArgumentException
s. It would, therefore, execute that catch clause.ArrayIndexOutOfBoundsException
. Using the exception hierarchy in Figure 10.3.1, determine which of the following catch clauses could handle that exception.catch (RunTimeException e)
RunTimeException
is a superclass of ArrayIndexOutOfBoundsException
.catch (StringIndexOutOfBoundsException e)
catch (IndexOutOfBoundsException e)
IndexOutOfBoundsException
is a superclass of ArrayIndexOutOfBoundsException
.catch (Exception e)
catch (ArrayStoreException e)
random()
is called it returns 0.98, and the second time it is called it returns 0.44. What output would be printed by the program? What would be output if printStackTrace()
were called in addition to printing an error message? public class MyClass2 {
public void method1(double X) {
if (X > 0.95)
throw new ArithmeticException(X
+ " is out of range");
System.out.println("Hello " + X);
}
public void method2(double Y) {
if (Y > 0.5)
throw new ArithmeticException(Y
+ " is out of range");
System.out.println("Hello " + Y);
}
public static void main(String argv[]) {
MyClass2 myclass = new MyClass2();
try {
myclass.method1(Math.random());
myclass.method2(Math.random());
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
} // main()
} // MyClass2
MyClass2
program above, suppose that the first time random()
is called it returns 0.44, and the second time it is called it returns 0.98. What output would be printed by the program? What would be output if printStackTrace()
were called instead of printing an error message.public class BadDivide {
public void method1 (int n) {
method2(100, n);
}
public void method2 (int n, int d) {
System.out.println(n / d);
}
public static void main(String args[]) {
BadDivide bd = new BadDivide();
for (int k = 0; k < 5; k++)
bd.method1(k);
}
}
method2()
so that it handles the divide-by-zero exception itself, instead of letting Java handle it. Have it print an error message and a stack trace.someValue
equals 1000? int M = someValue;
try {
System.out.println("Entering try block");
if (M > 100)
throw new Exception(M + " is too large");
System.out.println("Exiting try block");
} catch (Exception e) {
System.out.println("ERROR: " + e.getMessage());
}
someValue
equals 50?try/catch
block that throws an Exception
if the value of variable X
is less than zero. The exception should be an instance of Exception
and, when it is caught, the message returned by getMessage()
should be “ERROR: Negative value in X coordinate.”