Object-Orientation - Exception Handling (in Java)
Beginner knows rules, but veterans know exceptions.
–Amit Kalantri, in Wealth of Words
Overview
Concept
An exception is a disruption to the normal control flow of of a program.
-
these typically occur when something unexpected has happened and are reported either during compilation or execution of the program.
-
for example, if Java code tries to open up a file that might not exist, an exception will be reported during compilation.
-
another example, if an array has a length of
5
and your code tries to access the array value at index position7
, an exception will be reported during execution. -
whether an exception is reported during compilation or execution depends upon whether it is checked or unchecked.
Types
Exceptions are divided into two types, depending upon when they appear.
-
Checked exceptions are reported during compilation, because the compiler checks for the them and crashes if they are not handled. Thus, any program that compiles has been programmed to gracefully handle any checked exceptions.
-
Unchecked exceptions are reported during execution. The compiler does not check for the them so it is possible the developers haven’t created any code to handle them, and so the program may crash if they occur during runtime. They are usually the result of poor programming.
-
It is wrong to think of exceptions as being errors. Errors, in Java, are catastrophic failures that result from factors outside the control of the program, such as running out of memory. Like unchecked exceptions, they cause the program to crash.
Exceptional lineage
The ancestry of Exception
and Error
objects is quite exceptional.
Throwable
|
|---> Exception
| |
| |---> IOException
| | |
| | |---> FileIOException
| |
| |---> RuntimeException
| |
| |---> NullPointerException
| |
| |---> IndexOutOfBoundsException
|
|---> Error
- All exceptions are descended from the Java API’s
Exception
class. - All unchecked exceptions are descended from
Exception
’s child,RuntimeException
. - Errors are descended from
Error
- All these inherit from a
Throwable
ancestor.
Why two types?
You may wonder why all exceptions aren’t checked by the compiler, so Java can avoid runtime crashes.
-
exceptions can occur frequently; if they were all checked (which requires extra code), it would put a big burden on the programmer and the code.
-
if that is the case, you may wonder why all exceptions aren’t left unchecked, so the programmer and codebase aren’t overburdened with checks…
-
… in some languages, like C++, all exceptions are unchecked. The compiler does not get involved and it is up to the programmer to make sure their code is reliable. Java is not one of these languages.
-
this discussion is known as The Java Unchecked Exceptions Controversy - read more about it.
Net net
In conclusion, the Java people recommend the following:
-
use checked exceptions if the program could reasonably be expected to recover from the situation in which the exception occurrs.
-
…and use unchecked exceptions if there would be no good way of programmatically recovering from the situation in which the exception occurrs.
-
our focus here is on understanding checked exceptions.
-
we must master the code used to create them, detect when they have occurred, and handle them so our programs do not unnecessarily crash.
Exception Handling
Options
Any method that includes code that might cause a checked exception to occur must handle that situation in one of two ways, in order to compile.
-
encapsulate the uncertain code within a
try
/catch
block. -
declare in the method signature that the method
throws
one or more exceptions.
Example - the problem
For example, take a method that opens a file… this might cause a checked exception if the file being opened does not exist.
public void doSomething() {
Scanner scn = new Scanner( new File( "foo.csv" ) );
// .. imagine we do something useful with this file here ...
}
-
This code will not compile. See for yourself!
-
The
Scanner
class’s constructor declares that it may cause a checked exception when the file is not found. It declares this using thethrows
declaration which we’ll return to a bit later. -
Yet the
doSomething()
method does not have any code to handle the situation in which this exception occurs.
Example - the try/catch solution
One way to get the code to compile would be to surround the code of concern within a try
/catch
block.
public void doSomething() {
try {
// attempt to open the file... this may or may not fail
Scanner scn = new Scanner( new File( "foo.csv" ) );
// .. imagine we do something useful with this file here ...
}
catch (FileNotFoundException e) {
// handle the situation in which the file was not found
System.out.println("So sorry, we couldn't open the file.");
// put whatever you want to do in this situation here.
}
}
Example - the throws solution
An alternative way to get that code to compile would have been to declare that the method throws
an exception.
public void doSomething() throws FileNotFoundException {
Scanner scn = new Scanner( new File( "foo.csv" ) );
// .. imagine we do something useful with this file here ...
}
-
this is a form of ‘passing the buck’.
-
the
doSomething()
method has abdicated responsibility for the exception that may occur, and it is now the responsibility of any method that calls this method to handle the exception. -
any method that calls this method must now itself handle the situation in one of the two ways we’ve discussed: using
try
/catch
around the call to this method, or declaring that it toothrows
an exception.
Example - the throws solution (continued)
Let’s imagine another method that calls our doSomething
method.
public void anotherMethod() {
doSomething(); // call the method that might trigger an exception
}
-
if
doSomething()
encapsulates the file opening code within atry
/catch
block, as in an earlier example, then this code is fine - no need for any worry, since the exception is fully handled. -
if
doSomething()
instead declared that itthrows
an exception, as in our most recent example, then this code will not compile. -
in the latter scenario, this method must either surround the call to
doSomething()
in atry
/catch
block, or declare that it toothrows
an exception.
Example - the throws solution (continued again)
Here are the two options for our anotherMethod()
in order for it to compile.
// the try/catch way
public void anotherMethod() {
try {
doSomething(); // call the method that might trigger an exception
}
catch (FileNotFoundException e) {
System.out.println("So sorry, we couldn't open the file.");
// put whatever you want to do in this situation here.
}
}
// the throws way
public void anotherMethod() throws FileNotFoundException {
doSomething();
}
Custom Exceptions
Concept
While the Java API comes with exception types useful for many common situations, it is possible to build one’s own exception types.
-
What we call an exception is simply an object of any type that is descended from the
Exception
class. -
If we subclass
Exception
, we can create our own checked exception types that we can thenthrow
whenever we want.
Subclassing Exception
Here is an example of a custom exception type, useful for the situation where a virtual person sips a burning hot virtual cup of coffeee.
public class BurnedMouthException extends Exception {
// .. put any code you want in here.
}
- elsewhere in our program, if a virtual person sips a burning hot cup of virtual coffee, we can throw an exception of this specialized type.
Throw and throws
Here is an example of the sip()
method in our Person
class that might throw one of our custom exceptions if the Coffee
object is burning hot.
public class Person {
public void sip(Coffee c) throws BurnedMouthException {
// throw an exception if the temperature is too hot
if ( c.getTemperature("Farenheit") > 113 ) {
throw new BurnedMouthException();
}
}
}
-
Note that the method must declare, with the
throws
keyword, that it might throw an exception. -
Note also that the exception is nothing but an object of a class descended from
Exception
and is thrown using thethrow
keyword.
Throwing multiple possible exceptions
It’s perfectly possible for a method to potentially throw more than one type of exception.
- For example, let’s say we had a
OutOfCoffeeException
that we also threw if sipping an empty coffee.
public void sip(Coffee c) throws BurnedMouthException, OutOfCoffeeException {
// throw an exception if the temperature is too hot
if ( c.getTemperature("Farenheit") > 113 ) {
throw new BurnedMouthException();
}
// throw an exception if the coffee is empty
else if ( c.coffeeRemaining() == 0 ) {
throw new OutOfCoffeeException();
}
}
Handling exceptions
As we have already learned, any other method that calls this sip()
method must handle the possibility of a checked exception occurring in one of two ways. Here is a try
/catch
solution:
try {
// let's imagine that we have Person object person and Coffee object coffee
person.sip(coffee);
}
catch (BurnedMouthException e) {
// do something in response to this exception being thrown
}
catch (OutOfCoffeeException e) {
// do something in response to this exeption being thrown
}
-
Notice that we can catch the two exceptions separately, allowing us to take different follow-up actions in each scenario.
Handling exceptions (continued)
Alternatively, of course, this method could have declared that it throws
the two exceptions that the sip()
method might throw, thereby passing the buck to any method that calls it.
public static void main(String[] args) throws BurnedMouthException, OutOfCoffeeException {
// imagine that we have Person object person and Coffee object coffee already instantiated
// have the person take a sip
person.sip(coffee);
}
-
Notice that this code compiles, even though we never fully handle the exceptions with a
try
/catch
block. -
The compiler ignores any exceptions thrown by the
main
method…. but the program crashes during runtime.
Conclusions
Like function arguments and return values, checked exceptions are yet another mechanism by which messages are passed from one part of a Java program to another.
- Thank you. Bye.