We have seen exception handling in Java as being comprised of exception definition via a class definition, exception handlers via try- and catch-blocks, and raising exception incidents by throwing appropriate objects.
A general framework for exception handling has been outlined in the previous section. Ideally, the framework should be extended to fit various scenarios, which leads us to present various usage patterns.
9.5.1 Multiple Handlers
To facilitate monitoring more than one exception condition, a try-block allows for multiple catch-clauses. Without the exception handling mechanisms of Java, error handling code would be untidy, and especially so for operations where a sequence of erroneous situations can occur. For example, when sending email to a user happy@xyz.com, an email client program must: initiate a socket connection to the host machine xyz.com; specify the recipient; and send the contents of the mail message.
Complications arise when xyz.com is not a valid email host, happy is not a le- gitimate user on the host, or premature closure of the socket connection.
class Evaluator { Stack s = new Stack();
...
void operand() { Integer value;
...
try {
value = (Integer) s.pop();
} catch (EmptyStack e) { // respond to empty stack condition value = new Integer(0);
} ...
} }
class EMail { ...
void send(String address) { errorCode = 0;
makeHostConnection(emailHostOf(address));
if (connectionError) { errorCode = 1;
} else {
verifyUser(emailUserof(address));
if (noUserReply {
errorMessage("host does not exist");
126 Object-Oriented Programming and Java
The above skeletal code for email processing may be structurally improved and made more transparent by using exception handling mechanisms. It is also useful from the maintenance point of view to separate processing logic from error process- ing. The code fragment below which uses multiple exception handlers is tidier if the appropriate exception objects are thrown by the methods makeHostConnection(), verifyUser() and sendContent().
The resultant structure is clearer—normal processing logic in the try-block, and error handling in catch-clauses.
errorCode = 2;
} else {
while ((!endofInputBuffer()) && errorCode != -1) { line = readInputBuffer();
sendContent(line);
}
if (networkError) { errorCode = 3;
} } } ...
}
class EMail { ...
void send(String address) { try {
errorCode = 0;
makeHostConnection(emailHostOf(address));
verifyUser(emailUserof(address));
while (!endofInputBuffer()) { line = readInputBuffer();
sendContent(line);
}
} catch (SocketException s) { errorCode = 1;
} catch (NoUserReply n) { errorCode = 2;
} catch (WriteError) { errorCode = 3;
} } ...
}
errorMessage("user is not valid");
errorMessage("connection error occurred");
errorMessage("user is not valid");
errorMessage("host does not exist");
errorMessage("connection error occurred");
Exception Handling 127
9.5.2 Regular Exception Handling
Where there are multiple code fragments with similar error handling logic, a global exception handler would again be neater.
In the code above, a transaction from an email client involves writing a mes- sage to the server and then reading if it receives an appropriate response. However, each message to the server might be unsuccessful due to a network error such as the termination of the connection.
With the exception handling mechanism in Java, generic errors may be handled by a common network error handler,for example, an IOException exception han- dler, so that such errors need not be constantly monitored.
class EMail { ...
void makeHostConnection(String host) { openSocket(host);
if (!IOerror()) { checkResponse();
giveGreetings();
} }
void giveGreetings() { if (IOerror()) errorCode = 9;
else
checkResponse();
}
void verifyUser(String user) { if (IOerror())
errorCode = 9;
else
checkResponse();
} ...
}
class EMail { ...
void send(String address) { try {
errorCode = 0;
makeHostConnection(emailHostOf(address));
verifyUser(emailUserof(address));
...
} catch (IOException x) { // network error detected }
}
void makeHostConnection(String host) { openSocket(host);
checkResponse();
writeMessage("HELO " + hostname);
writeMessage("VERIFY " + user);
128 Object-Oriented Programming and Java
9.5.3 Accessing Exception Objects
So far, we have discussed how a catch-block responds to exceptions specified in its parameter type T, but without reference to the parameter name e.
The fact that the parameter name of a catch-block is bound to the current exception object thrown allows for the means of transferring information to the exception handler.
9.5.4 Subconditions
We have seen that exception conditions are represented by objects that are described via class constructs. Since objects are dynamically distinguished from one another by their built-in class tags, this is a viable and productive method for representing dif- ferent conditions.
As with other classes, a new exception condition may also be subclassed from an existing class to indicate a more specific condition. Incorporating the inheritance mechanism to exception handling allows for logical classification and code reusabil- ity in both condition detection and handler implementation. CommError and Proto- colError in the skeletal fragment below are typical examples of rich representations using inheritance.
giveGreetings();
}
void giveGreetings() { checkResponse();
}
void verifyUser(String user) { checkResponse();
} }
try { ...
throw new X();
} catch (X e) {
... // e refers to exception object thrown earlier }
class CommError extends Exception { int errorKind;
Date when;
CommError(int a) ...
}
class ProtocolError extends CommError { int errorSource;
ProtocolError(int a, int b) ...
}
writeMessage("HELO " + hostname);
writeMessage("VERIFY " + user);
Exception Handling 129 The language mechanism that allows exception conditions and subsequently flow of control to propagate to an appropriate handler provides for powerful and flexible processing.
Due to inheritance rules, the exception handlers of the above try-block are ordered so that specific (subclass) exceptions are caught first. If generic (superclass) exceptions were caught first, the handler for the specific exceptions would never be used.
Note that while the exception object thrown is caught within the same try- block in the above, in practice, a throw-statement may also be deeply nested within methods invoked from the try-block.
9.5.5 Nested Exception Handlers
Since an exception handler comprises mainly of a statement block, the sequence of statements within it may also contain other try-blocks with associated nested catch- blocks.
As illustrated above, the scenario occurs when exceptions are anticipated within exception handlers.
try { ...
throw new CommError(errorCode);
...
throw new ProtocolError(errorCode, extraInformation);
...
} catch (ProtocolError e) {
... // handle ProtocolError by inspecting e appropriately } catch (CommError f) {
... // handle CommError by inspecting f appropriately }
try { ...
throw new X(errorCode);
...
} catch (X f) { ...
try { ...
throw new Y(errorCode, m);
...
} catch (Y e) { ...
} }
130 Object-Oriented Programming and Java
9.5.6 Layered Condition Handling
Just as catch-blocks may be nested, we consider a related situation where a more specific exception handling is required. This can occur when the current handlers are not sufficient, and the new try-block is nested within another to override it.
There are two applicable exception handling paradigms here: the nested handler may perform all necessary processing so that the enclosing handler does not realize that an exception has occurred; or the nested handler may perform processing rele- vant to its conceptual level and leave the remaining processing to the outer handler.
The former has been illustrated in the previous fragment, while the latter has been outlined in the framework below, where the nested handler throws the same exception after sufficient local processing.