This chapter is taken from the book - Getting started with Java programming language (https://www.amazon.com/dp/1544614519/). You can download the code for the book from here: https://drive.google.com/file/d/0B1IwsLB5TOglZXYxWW9JMndUX3M/view
Chapter 8 - Exception Handling
8-1 Introduction
We saw in chapter 6 that an exception represents a problem with
the program that results in immediate termination of the program. In this
chapter, we’ll look at:
> exceptions that you’ll normally come across
while writing Java programs
> checked
and unchecked exceptions
> how to create custom exceptions
> how to catch exceptions and handle them
gracefully
Let’s look at what are exception classes and how they are used in
programs.
8-2 Exception classes
In Java, an exception is represented by a class. Java defines
built-in exception classes representing common exception conditions. For
instance, java.lang.NullPointerException represents an exception condition that occurs
when you access attributes and methods of a reference type variable set to null.
When an exception occurs during program execution, following
things happen:
> the program execution is halted
> an appropriate exception object representing the
exception condition is created (also
referred to as thrown), and
> the newly created exception object is returned to the calling method
All exception classes in Java directly or indirectly inherit from java.lang.Throwable class. The following figure shows some of the important direct and indirect subclasses of Throwable class:
Figure 8-1 The direct and indirect subclasses of Throwable class
The above figure shows that java.lang.Error and java.lang.Exception are subclasses of Throwable. The Error and its subclasses represent unrecoverable exceptions; that is, exceptions that a program can’t recover from. For instance, an object of java.lang.OutOfMemoryError class (a subclass of Error) is thrown if JVM cannot create an object in the heap memory area because there is no memory space left in the heap memory area. A program can’t recover from java.lang.OutOfMemoryError exception.
The Exception and its subclasses represent
exceptions that a program can recover
from. For instance, an object of java.lang.NullPointerException
class (a subclass of Exception) is thrown when a method is
called on a reference type variable set to null. A program
can recover from java.lang.NullPointerException.
Exception’s subclasses are divided
into two categories:
> checked
exception classes – a checked exception must be handled in the program.
Excluding java.lang.RuntimeException, the remaining subclasses of Exception
class represent checked exceptions. For instance, java.lang.ClassNotFoundException
is an example of checked exception.
> unchecked
exception classes – an unchecked exception is not required to be handled in the
program. An unchecked exception is represented by java.lang.RuntimeException
and its subclasses. For instance, java.lang.NullPointerException
is an example of unchecked exception.
NOTE You can create new checked
and unchecked exceptions by subclassing Exception and RuntimeException
classes, respectively.
Let’s look at some common exceptions that are thrown by Java
programs.
IMPORT It is recommended that
you import the exceptions project into your Eclipse
IDE.
java.lang.NullPointerException
The following listing shows the NullReference
class whose main method calls area method of Rectangle
object:
Listing
8-1 NullReference class – throws NullPointerException
Project:
exceptions
Source
location: src\com\sample\NullReference.java
1 package com.sample;
2
3
public class NullReference {
4
private static Rectangle
rectangle;
5
6
public static void main(String args[]) {
7
int area = rectangle.area(); java.lang.NullPointerException
8
System.out.println("Area: " + area); not executed
9
}
10
}
On line #4, rectangle static
variable of type Rectangle is defined. And, on line #7,
Rectangle object’s area method is
called. If you run NullReference’s main
method, you’ll get the following output:
Exception in thread "main" java.lang.NullPointerException
at
com.sample.NullReference.main(NullReference.java:7)
The output shows that an exception
occurred on line #7 of NullReference class, and the exception
is java.lang.NullPointerException. As "Area:
" was not shown in the output, it means the statement System.out.println("Area: " + area)
of NullReference class (refer line #8) was not executed.
A NullPointerException
is thrown when you call methods or access attributes using a reference type variable
that is set to null. As we didn’t create and assign a
Rectangle object to the rectangle
variable, the rectangle variable was set to null
at the time area method was called on line #7.
Let’s now modify the NullReference class’s main
method such that it doesn’t throw NullPointerException
when the rectangle variable is set to null.
Hands-on
Modify NullReference’s
main method
to avoid NullPointerException
To avoid NullPointerException,
check if the reference type variable is set to null or not
before calling any methods or accessing any attributes.
The following listing shows the modified
NullReference class whose main method
calls area method only if the rectangle
variable is not set to null:
Listing
8-2 NullReference class – doesn’t throw NullPointerException
1 package com.sample;
2
3
public class NullReference {
4
private static Rectangle
rectangle;
5
6
public static void main(String args[]) {
7
if(rectangle == null) {
8 System.out.println("rectangle
variable doesn’t refer to a Rectangle object");
9 } else {
10
int area = rectangle.area();
11 System.out.println("Area:
" + area);
12 }
13
}
14
}
On line #7, we check if the rectangle
variable is set to null. As rectangle
variable is set to null, the statements on line #10 and
#11 are not executed. So, if you now
run the main method of NullReference
class, you’ll get the following output:
rectangle variable doesn’t refer to a Rectangle object
java.lang.ClassCastException
The following listing shows WrongCasting
class whose main method performs upcasting and downcasting of
objects:
Listing
8-3 WrongCasting class – throws ClassCastException
Project:
exceptions
Source
location: src\com\sample\WrongCasting.java
1 package com.sample;
2
3
public class WrongCasting {
4
public static void main(String args[]) {
5
Object rectangle = new
Rectangle(10, 10);
6
String str = (String) rectangle; throws java.lang.ClassCastException
7
System.out.println("String str : " + str); not executed
8
}
9 }
On line #5, an object of Rectangle
class is created and assigned to Object type
variable. On line #6, rectangle variable that holds
reference to the Rectangle object is assigned to a String
type variable. If you run WrongCasting’s main
method, you’ll get the following output:
Exception in thread "main" java.lang.ClassCastException: com.sample.Rectangle cannot be cast to
java.lang.String
at
com.sample.WrongCasting.main(WrongCasting.java:6)
The output shows that an exception
occurred on line #6 of WrongCasting class, and the exception
is java.lang.ClassCastException. As "String
str: " was not printed, this means that statement System.out.println("String str: " +
str) of WrongCasting class (refer line #7) was
not executed. Notice that the cause
of exception is also printed: "com.sample.Rectangle
cannot be cast to java.lang.String".
A ClassCastException
is not thrown if you cast an object
to a type which is either of the same
type as the object or it is supertype
of the object. In all other cases, a ClassCastException
is thrown. As String is not a supertype of Rectangle, the
assignment fails on line #6 and ClassCastException
is thrown.
NOTE As
explained in section 7-4 of chapter 7, you can use the instanceof
operator to check the runtime type of an object before performing downcasting
or upcasting of the object. This means, you can use the instanceof
operator to avoid java.lang.ClassCastException.
java.lang.ArithmeticException
The following listing shows DivisionByZero
class whose main method divides an integer number by 0:
Listing
8-4 DivisionByZero class – throws ArithmeticException
Project:
exceptions
Source
location: src\com\sample\DivisionByZero.java
1 package com.sample;
2
3
public class DivisionByZero {
4
public static void main(String args[]) {
5
int i = 1/0; throws java.lang.ArithmeticException
6
System.out.println("Value of i is " + i);not executed
7
}
8 }
On line #5, integer 1
is divided by 0 and the result is assigned to i.
If you run DivisionByZero’s main method,
you’ll get the following output:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at
com.sample.DivisionByZero.main(DivisionByZero.java:5)
The output shows that an exception
occurred on line #5 of DivisionByZero class, and the
exception is java.lang.ArithmeticException. As "Value
of i is " was not printed, this means that statement System.out.println("Value of i is "
+ i) of DivisionByZero class (line #6) was not executed. Notice that the cause of exception
is also printed as " / by zero ".
Now that we’ve seen some of the built-in
exception types in Java, let’s look at what happens when an exception is thrown
by a method.
8-3 Exception propagation and handling
In Java applications, a method typically
calls one or more methods of other objects in the application. When an
exception occurs, an exception object is returned to the calling method. A
method is said to handle an
exception, if it catches the
exception and stops it from propagating to the calling method.
Let’s say that the main
method (which acts as the entry point into a Java application) calls method x
of object X, and method x calls method y
of object Y, and method y calls method z
of object Z.
Figure 8-2 An example method call chain
Figure 8-2 An example method call chain
If an exception occurs in method z,
the exception is returned to method y if method z
can’t handle it. If method y can’t handle the exception, it
is returned to method x. If method x
can’t handle the exception, it is returned to the main method. If
the main method can’t handle the exception, it is handled by the JVM. The JVM catches the
exception and writes the exception details (which include exception type,
object which caused the exception, and the line number on which the exception
occurred) to the output.
Let’s look at an example that shows how unhandled
exceptions propagate to the calling method.
The following figure shows objects and
methods that are responsible for performing fund transfer in a banking
application:
Figure 8-3 A banking application’s objects and their methods
Figure 8-3 A banking application’s objects and their methods
In the above figure, FundTransfer
class defines transferFunds method that transfers
money from one account to another, and Account class represents
a bank account that defines credit and debit
methods. The steps followed for transferring funds from account xAcc
to account yAcc are:
> InternetBanking’s
main method calls FundTransfer’s transferFunds
method (represented by line labelled ❶) to transfer amt amount from
xAcc to yAcc account. xAcc
and yAcc are Account objects
representing the bank accounts involved in the fund transfer.
> FundTransfer’s transferFunds
method calls xAcc’s debit method (represented
by line labelled ❷)
to debit the amt amount from it, and calls yAcc’s
credit method (represented by line labelled ❸) to credit amt
amount to it.
IMPORT It is recommended that
you import the exception-propagation project into
your Eclipse IDE.
The following listing shows the FundTransfer class:
Listing
8-5 FundTransfer class
Project:
exception-propagation
Source
location: src\com\sample\FundTransfer.java
1 package com.sample;
2
3
public class FundTransfer {
4
public static boolean
transferFunds(Account xAcc, Account yAcc, int amt) {
5
xAcc.debit(amt);
6
yAcc.credit(amt);
7 System.out.println("Completed fund transfer");
8
return true;
9
}
10
}
The transferFunds
method is a static method that debits amt amount from
xAcc account and credits amt amount to yAcc
account. After successful transfer, the method prints the message "Completed
fund transfer" and returns true
in the end.
The following listing shows the Account
class that represents a bank account:
Listing
8-6 Account class
Project:
exception-propagation
Source location: src\com\sample\Account.java
1
package com.sample;
2
3
public class Account {
4
private int accountNumber;
5
private int currentBalance;
6
.....
7
public void credit(int amt) {
8
currentBalance = currentBalance + amt;
9
}
10
11
public void debit(int amt) {
12
int tempBalance = currentBalance - amt;
13
if (tempBalance < 0) {
14 int i = 1 / 0; throws java.lang.ArithmeticException
15 System.out.println(i); never executed
16
}
17
currentBalance = tempBalance;
18
}
19
.....
20 }
The accountNumber
and currentBalance instance variables represent the unique
bank account number and the current account balance, respectively. The credit
method (line #7) adds the given amt amount to currentBalance.
The debit method (line #11) needs to meet the following
requirements:
> As the bank account can’t have negative balance,
the debit method subtracts amt amount (the
amount to be debited) from currentBalance only if it
doesn’t lead to negative balance. For this reason, tempBalance
variable is defined on line #12 to temporarily hold account balance after
subtracting amt from currentBalance.
If tempBalance < 0 condition evaluates to false
on line #13, the tempBalance value is assigned to currentBalance
on line #17.
> If debiting amt amount can
lead to negative balance, the execution of debit method must stop. To achieve this requirement,
if tempBalance < 0 (refer line #13) condition evaluates
to true, we divide 1 by 0 (refer line
#14). We saw earlier that division by 0 results in java.lang.ArithmeticException
(refer listing 8-4). This means, if tempBalance < 0
evaluates to true, ArithmeticException will be thrown on
line #14 and the execution of debit method
will stop. This also means that the statement System.out.println(i)
on line #15 will never be executed.
The following listing shows InternetBanking
class whose main method calls FundTransfer’s transferFunds
method:
Listing
8-7 InternetBanking class
Project:
exception-propagation
Source location: src\com\sample\InternetBanking.java
1
package com.sample;
2
3
public class InternetBanking {
4
public static void main(String args[]) {
5
Account xAcc = new Account(1, 1000);
6
Account yAcc = new Account(2, 1200);
7
8
FundTransfer.transferFunds(xAcc, yAcc, 1400);
9 System.out.println("xAcc’s current
balance " + xAcc.getCurrentBalance());
10 System.out.println("yAcc’s current
balance " + yAcc.getCurrentBalance());
11
System.out.println("Completed
execution of main method");
12
}
13 }
At first, main method
creates two bank accounts xAcc and yAcc
with current balances as 1000 and 1200,
respectively. It then calls FundTransfer’s transferFunds
method (line #8) to transfer 1400 from xAcc
account to yAcc account. If you run InternetBanking’s
main method, you’ll get the following output:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.sample.Account.debit(Account.java:19)
at com.sample.FundTransfer.transferFunds(FundTransfer.java:6)
at com.sample.InternetBanking.main(InternetBanking.java:8)
NOTE The above
output is referred to as exception stack
trace. An exception stack trace provides details of methods (and their
classes) that were being executed when the exception occurred.
As xAcc account’s
balance is 1000 and we are trying to transfer 1400,
call to xAcc’s debit method throws ArithmeticException
(refer line #14 in listing 8-6). The output shows:
> Account
object’s debit method throws ArithmeticException.
As the debit method doesn’t handle the exception, it is returned
to the calling transferFunds method.
> As FundTransfer’s transferFunds
method doesn’t handle the ArithmeticException, it is returned
to the calling main method.
> As InternetBanking’s
main method doesn’t handle the ArithmeticException,
it is handled by the JVM. The above output is written by the JVM, which
includes the exception thrown and the methods that were in the call chain when
the exception occurred.
Notice that "xAcc’s current balance",
"yAcc’s current balance", "Completed
execution of main method" (refer InternetBanking
class in listing 8-7) and "Completed fund transfer"
(refer FundTransfer class in listing 8-5) messages are not printed on the output. This is
because the ArithmeticException thrown by debit method
not only stops execution of the debit method
but also stops execution of calling methods (that is, transferFunds
and main methods).
Figure 8-4 shows how the executions of main,
transferFunds and debit methods
are effected when ArithmeticException occurs. The figure
uses numbered balls to represent flow of program control. At ❶, InternetBanking’s main
method calls FundTransfer’s transferFunds
method. At ❷, FundTransfer’s transferFunds
method calls Account’s debit method.
At ❸, ArithmeticException
is thrown by the debit method which results in transfer
of control back to the statement of transferFunds method
that called the debit method. As transferFunds
method can’t handle the ArithmeticException, control is
returned to the statement of main method that called the transferFunds
method (refer ❹). As the main
method can’t handle the ArithmeticException, the control is
returned to the JVM for handling the exception (refer ❺).
Figure 8-4 Flow of program control when ArithmeticException is thrown by Account’s debit method
Figure 8-4 Flow of program control when ArithmeticException is thrown by Account’s debit method
The greyed-out statements shown in the above figure are not executed because the exception stops
execution of debit, transferFunds
and main methods.
Let’s now look at how to catch the ArithmeticException thrown by Account’s debit method.
Catching exceptions
To catch exceptions, you need to do the following:
> enclose statements that may throw an exception
in a try statement block
> immediately following the try
statement block, specify one or more catch statement
blocks that contain statements to be executed when an exception occurs
The following is the syntax for specifying try-catch
block:
1 try {
2
<statement-1>;
3
<statement-2>;
4
.....
5 }
6 catch(<exception-type> <name>)
{
a)
<catch-statement1>;
b)
.....
9 }
10 catch(<exception-type> <name>)
{
11
<catch-statement2>;
12
.....
13 }
The try block encloses
the statements that may throw an exception during execution. The catch
statement blocks are executed only when
an exception is thrown by a statement in the try block. The <exception-type> specifies the
exception type (like, NullPointerException and ClassCastException)
for which the catch block is executed, and <name> specifies the name with
which the exception object is available in the catch block. The
statements contained in the catch block specify how the exception
is handled.
Let’s add try-catch
block to Account’s debit method (refer
listing 8-6) to handle ArithmeticException.
Hands-on
Add try-catch block
As shown in the following listing, add try-catch
block to the Account class of exception-propagation
project:
Listing 8-8 Account class – try-catch
block for catching ArithmeticException
1
package com.sample;
2
3
public class Account {
4
.....
5
public void debit(int amt) {
6
try {
7 int tempBalance = currentBalance - amt;
8 if (tempBalance < 0) {
9 int i = 1 / 0; throws java.lang.ArithmeticException
10 System.out.println(i);
11 }
12 currentBalance = tempBalance;
13
} catch (ArithmeticException ae) { catches ArithmeticException
14 System.out.println(ae); calls toString method of ArithmeticException object
15
}
16
}
17
.....
18 }
In the above listing, try
block encloses statements that debit amt amount from
currentBalance. If tempBalance < 0
evaluates to true (line #8), ArithmeticException
is thrown on line #9. The catch block (line #13) specifies
that it is executed when ArithmeticException is thrown by
any of the statements in the try block. The catch
block contains a single statement that prints the string returned by calling the
toString method of the ArithmeticException
object.
If you now run InternetBanking’s
main method, you’ll get the following output:
java.lang.ArithmeticException: / by zero
Completed fund transfer
xAcc’s current balance 1000
yAcc’s current balance 2600
Completed execution of main method
The "java.lang.ArithmeticException: / by zero"
message is written by System.out.println(ae)
statement inside the catch block (refer line #14 in listing
8-8). The "Completed
fund transfer" message is written by FundTransfer’s
transferFunds method (refer line #7 in listing 8-4). The
rest of the messages are printed by InternetBanking’s
main method (refer listing 8-6).
Figure 8-5 shows the flow of program control when ArithmeticException
thrown by Account’s debit method is
caught in the debit method itself.
At ❶, InternetBanking’s
main method calls FundTransfer’s transferFunds
method. At ❷, transferFunds
method calls Account’s debit method.
At ❸, ArithmeticException
results in execution of the catch block’s System.out.println(ae)
statement. ArithmeticException transfers program control directly to
the catch block; therefore, System.out.println(i)
and currentBalance = tempBalance statements are not executed. At ❹, debit
method completes execution and the program control returns to the transferFunds
method. At ❺, the transferFunds
method completes execution and the program control returns to the main
method. As both transferFunds and main
methods execute successfully, all of their statements are executed.
Figure 8-5 Flow of program control when ArithmeticException is caught in the debit method
Figure 8-5 Flow of program control when ArithmeticException is caught in the debit method
NOTE Figure 8-5 shows that the
statement contained in the catch block is executed only
when ArithmeticException is thrown by int
i = 1/0 statement in the try block.
We caught the ArithmeticException
thrown in debit method so that the program doesn’t terminate abruptly.
But, it doesn’t mean that the program did what it was intended to do. Figure
8-5 shows that currentBalance = tempBalance statement
is not executed. So, when we
transferred 1400 from xAcc to yAcc
account, the current balance of xAcc account
remained unchanged at 1000 but the current balance of yAcc
increased to 2600.
Hands-on
Modify placement of try-catch block
You should note that the placement of try-catch
block has an impact on the program behavior. For instance, modify the Account’s
debit method such that the try-catch
block is placed as shown in the following listing:
Listing 8-9 Account class – try-catch block
with a different placement
1
package com.sample;
2
3
public class Account {
4
private int currentBalance;
5 .....
6
public void debit(int amt) {
7
int tempBalance = currentBalance - amt;
8
if (tempBalance < 0) {
9 try {
10 int i = 1 / 0; throws ArithmeticException
11 System.out.println(i); this statement
is never executed
12 } catch (ArithmeticException
ae) {
13 System.out.println(ae);
14 }
15
}
16
currentBalance = tempBalance; executed even when ArithmeticException occurs
17
}
18
.....
19 }
The try-catch
block in the above listing only encloses the following two statements:
int i = 1/0;
System.out.println(i);
And, the following statement is now after
the try-catch block:
currentBalance
= tempBalance;
The statement(s) that follow after the try-catch
block are executed in the following scenarios:
> no exception is thrown by the statements in the try
block
> exception thrown by the statements in the try
block is caught by the catch block
As ArithmeticException thrown in the try block is caught by the catch block, the currentBalance = tempBalance statement is executed. This means that even if ArithmeticException occurs, the amt amount is subtracted from currentBalance. If you now execute the InternetBanking’s main method, you’ll see the following output:
java.lang.ArithmeticException: / by zero
Completed fund transfer
xAcc’s current balance -400
yAcc’s current balance 2600
Completed execution of main method
The output shows that when 1400
is transferred from xAcc to yAcc account, 1400
is debited from xAcc account and 1400
is credited to yAcc account. This program behavior is
incorrect because 1400 was debited from xAcc
account even when its current balance was only 1000.
Figure 8-6 shows the program behavior
when try-catch block is placed as shown
in listing 8-9.
Figure 8-6 The statement currentBalance = tempBalance is executed even when ArithmeticException is thrown
Figure 8-6 The statement currentBalance = tempBalance is executed even when ArithmeticException is thrown
NOTE A
statement inside a catch block can also throw an
exception. If an exception is thrown from the catch block,
the program execution stops and the exception is returned to the calling
method.
If the statements enclosed within the try
block can throw more than one type of exception, then you can use multiple catch
blocks to catch those exceptions.
IMPORT It is
recommended that you import the multiple-catch project
into your Eclipse IDE.
The following listing shows Sample
class that uses multiple catch blocks:
Listing
8-10 Sample class
– multiple catch blocks
Project:
multiple-catch
Source location: src\com\sample\Sample.java
1
package com.sample;
2
3
public class Sample {
4
private int x;
5
private Car car;
6
7
public void doSomething() {
8
try {
9 int i = 1 / x; can throw java.lang.ArithmeticException
10 car.getColor(); can throw java.lang.NullPointerException
11
} catch (ArithmeticException ae)
{
12 System.out.println("exception
: " + ae); executed when ArithmeticException is thrown
13
} catch (NullPointerException
npe) {
14 System.out.println("exception
: " + npe); executed when NullPointerException is thrown
15
}
16
}
17
public void setX(int x) {
18
this.x = x;
19
}
20
public void setCar(Car car) {
21
this.car = car;
22
}
23 }
The values of x
(of type int) and car (of type Car)
attributes are set via setX (line #17) and setCar
(line #20) methods, respectively. The doSomething
method assigns value of 1/x to variable i
(line #9) and calls Car’s getColor method
(line #10).
If we create an instance of Sample
and set value of x to 0, java.lang.ArithmeticException
is thrown on line #7 when the doSomething
method is executed. Similarly, if create an instance of Sample
and set car attribute to null, java.lang.NullPointerException
is thrown on line #8 when the doSomething
method is executed. As we want to catch both the exceptions, a catch
block has been defined for each exception type (refer line #11 and #13).
NOTE As only
one exception is thrown at a time by a program, only one catch
block is executed.
The following listing shows the SampleTest
class whose main method creates objects of Sample
class and calls their doSomething method:
Listing
8-11 SampleTest class
Project:
multiple-catch
Source location: src\com\sample\SampleTest.java
1
package com.sample;
2
3
public class SampleTest {
4
public static void main(String args[]) {
5
Sample sample_1 = new Sample();
6
sample_1.setX(1);
7
sample_1.doSomething();
8
9
Sample sample_2 = new Sample();
10
sample_2.setCar(new
Car("someMake"));
11
sample_2.doSomething();
12
}
13 }
In the above listing, we create two
instances, sample_1 and sample_2, of Sample
class. When a Sample object is created by calling Sample
class’s default constructor, the value of x is
initialized to 0 and the car attribute
is initialized to null. We call sample_1’s
setX method (line #6) to set its value of x
to 1 and we call sample_2’s setCar
method (line #10) to set its car attribute to a Car
object.
As sample_1’s car
attribute is set to null, call to sample_1’s
doSomething method results in NullPointerException
(refer line #10 in listing 8-10). Similarly, as sample_2’s x
attribute is set to 0, call to sample_2’s doSomething
method results in ArithmeticException (refer line #9 in
listing 8-10).
If you run SampleTest’s main
method, you’ll get the following output:
exception java.lang.NullPointerException
exception java.lang.ArithmeticException: / by zero
The output corresponds to the System.out.println
statement contained in each catch block defined in Sample’s
doSomething method (refer line #12 and #14 in listing
8-10).
Catching multiple exceptions in a single catch block
Instead of defining a catch
block corresponding to each exception type, you can define a single catch block
that catches multiple exception types. The following listing shows the modified
doSomething method of Sample class
that uses a single catch block to catch both NullPointerException
and ArithmeticException types:
Listing 8-12 Catching multiple exceptions in a single catch
block
1
public void doSomething() {
2
try {
3
int i = 1 / x;
4
car.getColor();
5
} catch (ArithmeticException |
NullPointerException ex) { |
separates exception types
6
System.out.println("exception " + ex);
7
}
8 }
In the above listing, catch
block specifies that it catches both ArithmeticException
and NullPointerException types. The vertical bar |
is used to separate different exception types caught by the catch
block.
NOTE Using a
single catch block for catching exceptions is useful only if you
want to execute same set of
statements for the caught exceptions
Catching exceptions related by inheritance
By default, a catch
block catches all exceptions of the specified exception type and its subtypes. For instance, the following
listing shows the modified version of Sample’s doSomething
method that catches java.lang.Exception type instead of NullPointerException
and ArithmeticException types:
Listing 8-13 Catching multiple exceptions
1
public void doSomething() {
2
try {
3
int i = 1 / x; can throw java.lang.ArithmeticException
4
car.getColor(); can throw java.lang.NullPointerException
5
} catch (Exception ex) {
6
System.out.println("exception " + ex);
7
}
8 }
The catch block
specifies that it catches java.lang.Exception type. As
both ArithmeticException and NullPointerException
are subtypes of java.lang.Exception, the catch
block catches both ArithmeticException and NullPointerException
exceptions.
NOTE Both java.lang.ArithmeticException
and java.lang.NullPointerException classes inherit from java.lang.RuntimeException,
which in turn inherits from java.lang.Exception. This means,
ArithmeticException and NullPointerException
classes are subtypes of Exception.
When an exception occurs, the first
catch block that matches the thrown exception is
responsible for handling the exception. In the following listing, if an ArithmeticException
is thrown on line #3, it is handled by the first catch block
(line #5):
Listing 8-14 The first catch block handles the
exception
1 public void doSomething() {
2
try {
3
int i = 1 / x; can throw java.lang.ArithmeticException
4 car.getColor(); can throw java.lang.NullPointerException
5 }
catch (ArithmeticException ex) {
6 System.out.println("exception
" + ex);
7 }
catch (Exception ex) {
8 System.out.println("exception
" + ex);
9 }
10 }
The catch block
corresponding to java.lang.Exception type (line #7) is
executed only when an exception other than ArithmeticException
(or its subtype) is thrown.
Now, consider the following listing in
which the catch block for java.lang.Exception
comes before the catch
block for java.lang.ArithmeticException:
Listing 8-15 Exception is caught before ArithmeticException
1 public void doSomething() {
2
try {
3
int i = 1 / x; can throw java.lang.ArithmeticException
4 car.getColor(); can throw java.lang.NullPointerException
5 }
catch (Exception ex) {
6 System.out.println("exception
" + ex);
7 }
catch (ArithmeticException ex) { compilation
error
8 System.out.println("exception
" + ex);
9 }
10 }
If an ArithmeticException
is thrown on line #3, it’ll be caught by the first catch block
(line #5) that catches java.lang.Exception. This means, the catch
block for ArithmeticException (line #7) will never be executed. As the Java compiler figures out that the catch
block for ArithmeticException will never be executed, it reports
compilation error on line #7. This shows that the catch blocks
must be ordered such that a more general exception (like java.lang.Exception)
comes after a more specific exception
(like java.lang.ArithmeticException).
Let’s now look at the finally
statement block that comes after the catch
block.
IMPORT It is
recommended that you import finally-block project into your
Eclipse IDE.
finally block
The finally block comes after the catch
block(s) and is guaranteed to be
executed. This means, the finally block is executed when no exception occurs and also when the try
or catch block throws an exception.
The following listing shows the Sample class
whose doSomething method uses the finally block:
Listing 8-16 Sample
class - finally block
Project:
finally-block
Source location: src\com\sample\Sample.java
1
package com.sample;
2
3
public class Sample {
4
private int x;
5
6
public void doSomething() {
7
int i = 10;
8
try { =
9 i = 100 / x; throws ArithmeticException if x is 0
10
} catch (ArithmeticException ae) {
11 System.out.println("exception " + ae);
12
} finally {
13 System.out.println("value of i " + i); guaranteed to
be executed
14
}
15
}
16
17
public void setX(int x) {
18
this.x = x;
19
}
20 }
Sample class
defines an instance variable x (of type int)
whose value is set using setX method (line #17). The doSomething
method defines an int variable i
and initializes it to 10 (refer line #7). The value of i
is then changed to 100/x (line #9). If x
is 0, then the statement i = 100/x will
throw java.lang.ArithmeticException. For this reason, the
statement i = 100/x is enclosed within the try
block. The catch block on line #10 catches the ArithmeticException
and writes the exception details to the output. The catch block is
followed by a finally block (line #12) which simply
writes the value of i to the output.
The following listing shows the SampleTest
class whose main method creates two instances (sample_1
and sample_2) of Sample class
and calls their doSomething method:
Listing 8-17 SampleTest
class
Project:
finally-block
Source location: src\com\sample\SampleTest.java
1
package com.sample;
2
3
public class SampleTest {
4
public static void main(String args[]) {
5
Sample sample_1 = new Sample();
6
System.out.println("calling sample_1's doSomething");
7
sample_1.doSomething();
8
9
Sample sample_2 = new Sample();
10
sample_2.setX(2);
11
System.out.println("calling sample_2's doSomething");
12
sample_2.doSomething();
13
}
14 }
We call sample_1’s doSomething
method without setting the value of attribute x. As the
default value of x is 0, call to sample_1’s
doSomething method throws java.lang.ArithmeticException
(refer line #9 in listing 8-16). We call sample_2’s doSomething
method after setting the value of x to 2.
As the value of x is 2, calling sample_2’s
doSomething method doesn’t throw any exception.
If you run SampleTest’s main
method, you’ll see the following output:
calling sample_1's doSomething
exception java.lang.ArithmeticException: / by zero
value of i 10
calling sample_2's doSomething
value of i 50
The output shows that calling sample_1’s
doSomething method results in execution of both catch
and finally blocks. And, even though calling sample_2’s
doSomething method doesn’t result in an exception, the finally
block is still executed.
NOTE A
statement inside a finally block can also throw an
exception. If an exception is thrown from the finally block,
the program execution stops and the exception is returned to the calling
method.
Let’s now look at how to create custom
exception types and throw them.
8-4 Custom exception types
You can create a checked exception type by extending java.lang.Exception,
and you can create an unchecked
exception type by extending java.lang.RuntimeException.
NOTE You can
create an exception type by directly extending java.lang.Throwable.
An exception type created by extending java.lang.Throwable
is treated as an unchecked exception.
It is recommended that you don’t
create a custom exception type by directly extending java.lang.Throwable
class. Instead, use java.lang.Exception (for checked
exceptions) or java.lang.RuntimeException (for
unchecked exceptions).
Let’s now look at a modified version of
internet banking application that throws a custom InsufficientBalanceException
exception type when the bank account doesn’t have sufficient balance for
performing fund transfer.
IMPORT It is
recommended that you import the custom-exception
project into your Eclipse IDE.
The following listing shows the InsufficientBalanceException
class that represents insufficient balance in a bank account:
Listing
8-18 InsufficientBalanceException
class
Project:
custom-exception
Source location: src\com\sample\InsufficientBalanceException.java
1
package com.sample;
2
3
public class InsufficientBalanceException extends RuntimeException {
4
public InsufficientBalanceException(int
accountNumber, int balance) {
5
super("The bank account
" + accountNumber + " has balance " + balance);
6
}
7 }
InsufficientBalanceException
extends java.lang.RuntimeException; therefore, it’s an unchecked
exception type. InsufficientBalanceException’s constructor
accepts accountNumber (the bank account number) and balance
(current balance in the bank account) as arguments. InsufficientBalanceException’s
constructor calls RuntimeException’s constructor (line
#5) and passes the exception message. The exception message is stored as an
instance variable in the RuntimeException object, and can
be accessed by calling the inherited getMessage
method of RuntimeException.
The following listing shows the Account
class that throws InsufficientBalanceException if the
current balance (represented by currentBalance
instance variable) is less than the amount being transferred (represented by amt
argument passed to debit method):
Listing
8-19 Account class –
throwing custom exception
Project:
custom-exception
Source location: src\com\sample\Account.java
1
package com.sample;
2
3
public class Account {
4
private int accountNumber;
5
private int currentBalance;
6
.....
7
public void debit(int amt) {
8
int tempBalance = currentBalance - amt;
9
if (tempBalance < 0) {
10 throw new InsufficientBalanceException(accountNumber, currentBalance);
11
}
12
currentBalance = tempBalance;
13
}
14
.....
15 }
On line #8, the debit
method stores the result of subtracting amt amount from
currentBalance into tempBalance
variable. If tempBalance < 0 condition evaluates
to true, it means that subtracting amt
amount from currentBalance results in negative account balance. For
this reason, if tempBalance < 0 evaluates to true,
an object of InsufficientBalanceException class is created
using the new keyword and thrown using the throw
statement (refer line #10).
A throw statement
has the following syntax:
throw <a-Throwable-object>;
here,
<a-Throwable-object> is any
object that extends directly or indirectly java.lang.Throwable
If you now open the InternetBanking
class (which is same as shown in listing 8-7) of custom-exception
project and run its main method, you’ll get the following
output:
Exception in thread "main"
com.sample.InsufficientBalanceException: The bank account 1 has balance 1000
at
com.sample.Account.debit(Account.java:19)
at
com.sample.FundTransfer.transferFunds(FundTransfer.java:5)
at
com.sample.InternetBanking.main(InternetBanking.java:8)
The output shows that an attempt to
transfer 1400 from xAcc to yAcc
resulted in InsufficientBalanceException thrown by the Account’s
debit method. The exception message "The bank account 1 has balance 1000"
was set while creating the InsufficientBalanceException
object (refer line #5 of listing 8-18). The JVM obtains the exception message
associated with the InsufficientBalanceException object by
calling its getMessage method and writes it to the output.
NOTE You can use
throw statement inside catch and finally
blocks also to throw exceptions.
Let’s look at how the program would have
looked if we had defined InsufficientBalanceException as
a checked exception.
Hands-on
Modify InsufficientBalanceException class
to extend java.lang.Exception
The following listing shows the modified
InsufficientBalanceException class that extends from java.lang.Exception:
Listing 8-20 InsufficientBalanceException class – a checked
exception
1
package com.sample;
2
3
public class InsufficientBalanceException extends Exception {
4
public InsufficientBalanceException(int accountNumber, int balance) {
5
super("The bank account " + accountNumber + " has balance
" + balance);
6
}
7 }
As the InsufficientBalanceException class now inherits from java.lang.Exception
class, it represents a checked exception. As shown in figure 8-7, you’ll now
notice that the Account class shows compilation error
on the line where we throw InsufficientBalanceException. Account
class shows compilation error because its debit method
throws InsufficientBalanceException – a checked exception. The
compilation error message is 'Unhandled exception type
InsufficientBalanceException'.
We saw in earlier
examples that if you don’t catch an unchecked exception, it is simply
propagated to the calling method. On the other hand, a checked exception must be caught in the method or specified
in the method definition using the throws clause.
Figure 8-7 Account class fails to compile because InsufficientBalanceException is a checked exception
Figure 8-7 Account class fails to compile because InsufficientBalanceException is a checked exception
So, for successfully compiling the Account
class we must specify that the debit method
throws InsufficientBalanceException or catch it in the debit
method itself. The following listing shows the modified debit
method that specifies that it throws InsufficientBalanceException:
Listing 8-21 Account class’s
debit method – throws clause
usage
1
public void debit(int amt) throws
InsufficientBalanceException {
2
int tempBalance = currentBalance - amt;
3
if (tempBalance < 0) {
4
throw new InsufficientBalanceException(accountNumber, currentBalance);
5
}
6
currentBalance = tempBalance;
7 }
In the above listing, we’ve added the throws
clause to the debit method definition. The debit
method now clearly indicates that it throws InsufficientBalanceException
which the calling method can catch and attempt to recover from it.
NOTE You
should note that an unchecked exception represents a programming error (like,
division by zero); therefore, a calling method should not attempt to recover
from it. For this reason, throw an unchecked exception if you expect that the
calling methods can’t recover from it.
And, you should throw a checked exception if you expect that the calling
methods can recover from it.
Adding throws clause
to Account class’s debit method
results in compilation error in the FundTransfer
class (refer figure 8-8). The line on which we call Account’s debit
method shows compilation error because the debit method is
defined to throw InsufficientBalanceException checked
exception (refer listing 8-21). The transferFunds method
must either catch InsufficientBalanceException or
specify that it also throws InsufficientBalanceException.
Figure 8-8 FundTransfer
class fails to compile because the transferFunds
method doesn’t catch InsufficientBalanceException or
specify that it throws InsufficientBalanceException
NOTE When InsufficientBalanceException
was defined as an unchecked exception, it was not required for FundTransfer’s transferFunds
method to catch the exception or specify it using the throws
clause. This shows that if a method calls a method that throws a checked
exception, the calling method must either catch the checked exception or
specify it using the throws clause.
The following listing shows the modified transferFunds
method that uses the throws clause to specify that it
throws InsufficientBalanceException:
Listing 8-22 FundTransfer class
1
package com.sample;
2
3
public class FundTransfer {
4
public static boolean transferFunds(Account xAcc, Account yAcc, int amt)
5 throws InsufficientBalanceException {
6
xAcc.debit(amt);
7
yAcc.credit(amt);
8
System.out.println("Completed fund transfer");
9
return true;
10
}
11 }
The FundTransfer
class will now compile successfully.
NOTE If a
method throws multiple checked exceptions, you can specify comma-separated names
of those exceptions in the throws clause.
You’ll now notice that the InternetBanking’s
main method shows compilation error (refer figure 8-9) on
the line where the call is made to FundTransfer’s transferFunds
method. As the FundTransfer’s transferFunds
method specifies that it throws InsufficientBalanceException,
the main method must either catch the exception or specify it
using the throws clause.
Figure 8-9 InternetBanking
class fails to compile because it must either catch InsufficientBalanceException
or specify that the main method throws it
As we don’t want the InsufficientBalanceException
to propagate to the JVM, modify InternetBanking’s
main method to catch the exception, as shown here:
Listing 8-23 InternetBanking class – catching InsufficientBalanceException
1
package com.sample;
2
3
public class InternetBanking {
4
public static void main(String args[]) {
5
Account xAcc = new Account(1, 1000);
6
Account yAcc = new Account(2, 1200);
7
8
try {
9
FundTransfer.transferFunds(xAcc, yAcc, 1400);
10
} catch (InsufficientBalanceException
ex) {
11 System.out.println("Failed to transfer money. Reason: -->
" + ex.getMessage());
12 }
13
.....
14
}
15 }
In the above listing, call to FundTransfer’s
transferFunds method (line #9) is surrounded by try-catch
block. The catch block catches InsufficientBalanceException
and prints the exception message on the output (line #11). Notice that we’ve
explicitly called the getMessage method of InsufficientBalanceException
object to obtain the exception message.
If you run InternetBanking’s
main method, you’ll get the following output:
Failed to transfer money. Reason: --> The bank account 1 has balance
1000
xAcc’s current balance 1000
yAcc’s current balance 1200
Completed execution of main method
If we had let the InsufficientBalanceException
propagate to JVM, the output would have consisted of methods and Java classes
that come into picture when InsufficientBalanceException is
thrown. The above output is simple to understand and has relevant details that
tell why the program failed to transfer money from xAcc to yAcc
account. This graceful handling of exceptions is required in real-world Java applications.
QUIZ
8-1: Which of the following
statements are true about exceptions?
a)
an unchecked exception represents a programming
error
b)
a checked exception represents a recoverable
exception
c)
if an exception class extends from java.lang.Exception,
then it represents an unchecked exception
d)
if an exception class extends from java.lang.RuntimeException,
then it represents a checked exception
e)
java.lang.Error
and its subclasses represent unrecoverable
exceptions
8-2: Consider the following Calculator
class whose calculate method performs calculations on the supplied
number:
1
public class Calculator {
2
private int memory;
3
4
public void calculate(int i) {
5
memory = i;
6
memory = memory + 10; increment memory by 10
7
try {
8 memory = memory / 0; divide memory by 0
9
} catch (ArithmeticException ae) {
10 memory = memory + 10; increment memory by 10
11 memory = memory / 0; divide memory by 0
12
} finally {
13 memory = memory * 2; multiply memory by 2
14 memory = memory / 0; divide memory by 0
15
}
16
}
17
18
public int getMemory() {
19
return memory;
20
}
21 }
What would be the output from executing
the following main method?
1 public static void main(String args[]) {
2
Calculator calculator = new Calculator();
3
try {
4
calculator.calculate(5); call Calculator’s calculate method
5
} catch (Exception e) {
6
System.out.println(calculator.getMemory());
print the value of memory variable
7
}
8 }
8-3: Consider the following doSomething
method:
1
public void doSomething() {
2
try {
3
Object object = new
Rectangle();
4
String string = (String)object; object is downcasted to String
5
} catch(RuntimeException re)
{
6
System.out.println("RuntimeException");
7
} catch(ClassCastException cce)
{
8
System.out.println("ClassCastException");
9
}
10 }
What will be the output from executing
the doSomething method?
a)
"RuntimeException"
b)
"ClassCastException"
c)
"RuntimeException" followed by "ClassCastException"
d)
The program fails to compile
8-4: Consider the following MyException
and MyClass classes:
1
public class MyException extends ClassCastException
{
2
public MyException() {
3
super("MyException");
4
}
5 }
1 public class MyClass {
2
public void doSomething() {
3
try {
4
Object object = new
Rectangle();
5
String string = (String)object;
6
} catch(MyException me) {
7 System.out.println("catching
MyException");
8
} finally {
9 System.out.println("executing
finally block");
10
}
11
}
12 }
What would be the output from executing MyClass’s
doSomething method?
a)
"catching MyException"
b)
"executing finally block"
c)
"catching MyException" followed by
"executing finally block"
d)
MyClass fails
to compile
8-5: Consider the following CatchException class in which the catch
block catches java.lang.Throwable:
1 public class CatchException {
2
public void doSomething() {
3
try {
4
int i = 1/0; throws java.lang.ArithmeticException
5
} catch(Throwable throwable)
{ catches java.lang.Throwable
6 System.out.println("catching
java.lang.Throwable exception");
7
}
8
}
9 }
Which of the following statements are
correct about CatchException class?
a)
The class fails to compile because a catch
block can only catch exceptions that are of type java.lang.Exception
or its subtype
b)
ArithmeticException
thrown on line #4 is not caught by the catch block
8-5 Summary
In this chapter, we saw how you can throw and catch exceptions. A
real-world Java application typically defines some custom exceptions that are
specific to the problem domain. For instance, we defined InsufficientBalanceException
to represent insufficient balance in bank account. As incorrect placement of try-catch
block can result in incorrect result, you should carefully consider what
statements of a method should be enclosed within try block, and
what does the catch block should do in case an
exception occurs.
Comments
Post a Comment