Of course every method should validate its inputs. But what should a method do if it gets a bad input? There are four options:
1. Ignore it.
2. Flag it.
3. Display an error message, then gracefully terminate the program.
4. Repair it, then continue.
Although popular, the first option is just lazy programming.
The second option puts the burden on callers to check the error flag after every call to the method.
The third option is ideal for debugging code. Here's how it looks in Java:
class Account {
private double balance = 0;
public void withdraw(double amt) {
if (amt < 0) {
System.err.println("invalid amount");
System.exit(1);
}
if (balance < amt) {
System.err.println("insufficient funds");
System.exit(1);
}
balance -= amt;
}
// etc.
}
Here's the same idea in C++:
class Account {
private:
double balance;
public:
void withdraw(double amt) {
if (balance < amt) {
cerr << "invalid amount";
exit(1);
}
if (balance < amt) {
cerr << "insufficient funds";
exit(1);
}
balance -= amt;
}
// etc.
};
In both cases error messages are inserted into a special error stream to avoid redirecting error messages to a file at the command prompt. The exit function terminates the program and returns control to the operating system. The input to exit is called a termination code, 0 indicates normal termination and 1 indicate abnormal termination.
After the code is released the fourth option-- repair and continue—is the best. But this can lead to a problem: Often the place where an error is detected is not the place where an error can be repaired. For example, assume our Account class lives in a finance package that's used by a number of banking applications such as ATMs or online banking web sites:
Probably the banking application, where the user interface is probably located, is the place where errors should be repaired. Perhaps the state of the application can be rolled back to the last known good state and the user can be asked to start over. At the very least the user can be asked to save his documents and submit a bug report before the application shuts down.
But notice the problem. The finance package doesn't know about the banking package. How will it be able to tell it about the insufficient funds error or the invalid amount error?
An exception is an object that represents an error. Throwing an exception is similar to returning a value, except that instead of transferring control to the caller, throw unwinds the call stack and returns control to the first method that can handle the exception.
In Java all exceptions are subclasses of the exception hierarchy:
Exceptions are errors that programs might reasonably be expected to catch. Errors are things like "out of memory" that a reasonable program can't do anything about.
Most Java exceptions are checked exceptions. If a method might throw a checked exception, it must declare it:
class Account {
private double balance = 0;
public void withdraw(double amt) throws Exception {
if (amt < 0) {
throw new Exception("invalid amount");
}
if (balance < amt) {
throw new Exception("insufficient funds");
}
balance -= amt;
}
// etc.
}
C++ also has an exception hierarchy:
However, any object can be used as an exception in C++, even ordinary strings.
Declaring exceptions is optional in C++:
class Account {
private:
double balance;
public:
void withdraw(double amt) throws
(exception) {
if (amt < 0) {
throw exception("invalid amount");
}
if (balance < amt) {
throw exception("insufficient funds");
}
balance -= amt;
}
// etc.
};
If method A calls method B, and if B throws an exception, then A automatically throws the same exception. The programmer doesn't need to write any extra code, except Java programmers must declare that A might throw an exception:
class ATM {
public void transfer(Account source,
Account target, double amt)
throws
Exception {
source.withdraw(amt);
target.deposit(amt);
}
// etc.
}
Alternatively, a method can catch the exceptions that are thrown at it. This is done using a try-catch block.
class ATM {
public void transfer(Account source,
Account target, double amt)
throws
Exception {
source.withdraw(amt);
target.deposit(amt);
}
public void controlLoop() {
while(true) {
try {
Account s = getAccount(); //
prompt user for source
Account t = getAccount(); //
prompt user for target
double amt = getAmount(); //
prompt user for amount
transfer(s, t, amt);
} catch(Exception e) {
System.err.println(e.getmessage());
e.printStackTrace();
}
}
}
}
If an exception is thrown by a method called in the try block, control is immediately transferred to the first catch block (there can be many) that handles the type of exception thrown.
In our case the "invalid amount" or "insufficient funds" message would be printed, along with a stack trace, then control would pass back to the top of the while loop.
We can create custom hierarchies of exceptions. This saves exception catchers from parsing error messages to figure out what type of exception was thrown and therefore how to handle it.
For example, here's a hierarchy of exception classes that might be defined in the finance package:
We can revise our withdraw method to throw more specific exceptions:
class Account {
private double balance = 0;
public void withdraw(double amt)
throws InvalidAmount, InsufficientFunds
{
if (amt < 0) {
throw new InvalidAmount();
}
if (balance < amt) {
throw new InsufficientFunds;
}
balance -= amt;
}
// etc.
}
Here it is in C++:
class Account {
private:
double balance;
public:
void withdraw(double amt)
throws (InvalidAmount, InsufficientFunds)
{
if (amt < 0) {
throw InvalidAmount();
}
if (balance < amt) {
throw InsufficientFunds();
}
balance -= amt;
}
// etc.
};
The exception catcher can now handle different types of exceptions in different ways:
class ATM {
public void transfer(Account source,
Account target, double amt)
throws
InvalidAmount, InsufficientFunds {
source.withdraw(amt);
target.deposit(amt);
}
public void controlLoop() {
while(true) {
try {
Account s = getAccount(); //
prompt user for source
Account t = getAccount(); //
prompt user for target
double amt = getAmount(); //
prompt user for amount
transfer(s, t, amt);
} catch(InvalidAmount e) {
System.err.println(
"You entered a
negative amount, try again");
} catch(InsufficientFunds e) {
System.err.println("Sorry,
you're broke!");
} catch(FinanceException e) {
System.err.println(e.getmessage());
e.printStackTrace();
} catch(Exception e) {
System.err.println(
"A serious error has
occurred, shutting down");
System.exit(1);
}
}
}
}
Exceptions in Java