|
The Java Specialists' Newsletter
Issue 162 2008-06-10
Category:
Exceptions
Java version: Java 4,5,6 Exceptions in Javaby Dr. Heinz M. KabutzAbstract:
In this article, we look at exception handling in Java. We
start with the history of exceptions, looking back at the
precursor of Java, a language called Oak. We see reasons
why Thread.stop() should not be used and discover the
mystery of the RuntimeException name. We then look at some
best practices that you can use for your coding, followed
by some worst practices, in the form of exception
anti-patterns.
Welcome to the 162nd issue of The Java(tm) Specialists' Newsletter, sent to you from the
delightful Island of Crete. We went for a drive to the south
of the island yesterday and saw the most amazing sights.
Unfortunately I dropped my camera on Sunday evening, so you
will have to come and see the sights personally - no pictures
to tempt you :-) In two weeks time, I am
speaking at Jazoon
2008 in Switzerland, so please let me know if you will
be there.
Upcoming Java Specialist Master Courses:
- please click here to sign up.
As from May 2010, we are also offering this course on the island of Crete. We
only accept 6 students per class in Crete, due to the size of our conference
room. Please book early to avoid disappointment!
San Jose CA, Mar 16-19 2010, $3500 Ottawa, Canada, Mar 22-25 2010, $3500 Oslo, Norway, Apr 13-16 2010, Kr 24500 Montreal, Canada, Apr 20-23 2010, $3500 Toronto, Canada, May 17-20 2010, $3500 Chania, Crete, May 25-28, Jun 29-Jul 2 or Aug 24-27 2010, €2500
In-house courses if these dates or locations do not suit you - click here for more information. Exceptions in Java
Java was originally invented for embedded systems, such as
you would find in a video game console, a handheld GPS or a
cellular telephone. Faults in embedded systems can lead to
serious problems, even resulting in death. In order to deal
with faults properly, Java included a standard mechanism for
notifying threads of problems that occurred, in the form of
exceptions.
Oak
To understand some of the architectural choices in Java, it
is useful to take a step back and look at the precursor to
Java, a language called Oak.
The manual for Oak gives us hints as to why RuntimeException
has such a strange name.
Checked vs. Unchecked
During the last 10 years, I have taught advanced Java courses
to thousands of professional programmers and have
communicated with tens of thousands through conferences and
my Java Specialists' Newsletter. Up to now, not a single one
has been able to correctly answer the simple question:
"What does the name RuntimeException mean?"
Almost without fail, the answer I'm given is: "It is an
exception thrown at runtime."
However, aren't all exceptions thrown at runtime?
The answer can be found in the Oak manual, the precursor to
Java. Here we read: "Some exceptions are thrown by the Oak
runtime system." And in another paragraph: "If the object
being cast to a subclass is not an instance of the subclass
(or one of its subclasses), the runtime system throws an
InvalidClassCastException." Thus the name "RuntimeException"
originally meant that this was an exception thrown
by the Runtime to protect itself against damage.
The name "runtime" can also be seen in the class
java.lang.Runtime, which represents the Java Virtual Machine,
allowing us to find out information about memory usage,
manually invoke the garbage collector and launch external
processes.
In Oak, all exceptions were unchecked by the compiler. The
idea with checked exceptions was only introduced in Java.
My personal opinion is that checked exceptions are not as
useful as we would hope. For example, the class
java.io.IOException has 74 subclasses in Java 6. So whenever
you catch IOException, it could have been any one of 74
different error conditions. None of our code would cope with
all of them every time we catch IOException.
Asynchronous Exceptions
Another "feature" in Oak was the asynchronous exception.
This allowed a thread to cause an exception in another
thread, asynchronously. Thus in the middle of its work, a
thread would suddently experience an exception. In Oak, you
could "protect" code that was not safe to interrupt with such
an asynchronous exception. In the margin, we read the
comment: "The default will probably be changed to not allow
asynchronous exceptions except in explicitly unprotected
sections of code." When Sun moved from Oak to Java, they
kept asynchronous exceptions, but left out the ability to
protect regions of our code. Thus the default became
that everything was unprotected.
You have not heard of this asynchronous exception
mechanism in Java? Let me illustrate. Consider the
following code:
import java.sql.SQLException;
public class AsynchronousException {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
try {
while (true) {
System.out.println("Running");
}
} catch (SQLException ex) { // compiler error
System.err.println("We experienced a SQL Exception!");
}
}
};
t.start();
}
}
The reason this does not compile is because SQLException is a
checked exception, meaning that the compiler
checks that it could occur in this code. Since
while(true); does not throw any checked
SQLException, the compiler recognizes the catch clause as
dead code. Whilst this compile check is nice, it is not
entirely correct, due to asynchronous exceptions. Here is how
we could rewrite the try block:
try {
while (true) {
System.out.println("Running");
}
} catch (Exception ex) {
try {
throw ex;
} catch (SQLException ex2) {
System.err.println("We experienced a SQL Exception!");
} catch (Exception ex2) {
throw new RuntimeException(ex2);
}
}
We can now prove that the SQLException can happen by causing
an asynchronous exception:
import java.sql.SQLException;
public class AsynchronousException {
public static void main(String[] args)
throws InterruptedException {
Thread t = new Thread() {
public void run() {
try {
while (true) {
System.out.println("Running");
}
} catch (Exception ex) {
try {
throw ex;
} catch (SQLException ex2) {
System.out.println("We experienced a SQL Exception!");
} catch (Exception ex2) {
throw new RuntimeException(ex2);
}
}
}
};
t.start();
Thread.sleep(100);
// asynchronous SQLException
t.stop(new SQLException("Database is down!"));
Thread.sleep(100);
}
}
We can see some interesting effects if we run this program.
Our output can be the following:
Running
Running
Running
Running
RunningWe experienced a SQL Exception!
The reason for this we can find inside the
System.out.println(String)
method:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
Since Java does not have a protect keyword, the
asynchronous exception can literally happen anywhere
in your program, even inside atomic operations such as
println(). The
synchronized(this) is supposed to
protect multiple threads from calling the
println() method at the same time. Since it
synchronizes on "this", it is possible for
System.err and System.out
output to be interleaved, since they would be synchronizing
on different monitors.
On my machine, I sometimes saw this output:
Running
Running
Running
Running
RunningRunningWe experienced a SQL Exception!
I am not sure why this is, but it is not of great importance
to know exactly why this happens. As you have seen
asynchronous exceptions are a dangerous toy and should be
avoided. They break class invariants with no way to protect
yourself against them. With the interrupt() mechanism, at
least we can allow it to affect us in well-defined places.
Asynchronous exceptions have been deprecated
for a reason, so we advise against using them.
Best Practices
Beginners in Java are often unsure of how to use exceptions.
Common mistakes in code are blank catch clauses, a blanket
use of unchecked exceptions and the abuse of exceptions for
flow control. In this section, I will look at six "best
practices" for using exceptions in your Java code.
Don't Write Own Exceptions
When I first started using Java, I wrote my own custom
exceptions for everything. I would have a
CallDroppedException and an InsufficientFundsException. I
used exceptions when a simple return value would have been
preferable. In my latest Java project, I managed to create
not a single new exception class, but to rather reuse what
was there already.
Here are some useful exceptions that you could reuse, instead
of creating your own:
IllegalStateException
UnsupportedOperationException
IllegalArgumentException
NoSuchElementException
NullPointerException
You obviously need to ensure that the exception you are
throwing is representative of your error condition. Don't
throw an UnsupportedException when the real error condition
is that the database has shut down.
Write Useful Exceptions
There are occasions where we break our first best practice.
For example, if we want to have a more embracing exception
that can signify that any one of several problems has
occurred.
When we do need to write our own exceptions, we should follow
the naming convention of always ending all our classes with
"Exception". Our fields should be final, which is a good rule
for most classes to make concurrency easier.
Our own exception classes should contain more information
than just a simple String. For example, if you want to define
an exception for stating that a value is out of range, you
could define an exception class that not only contains the
incorrect value, but also the range that would have been
good.
For example, consider the following class:
public class OutOfRangeException
extends IllegalArgumentException {
private final long value, min, max;
public OutOfRangeException(long value, long min, long max) {
super("Value " + value + " out of range " +
"[" + min + ".." + max + "]");
this.value = value;
this.min = min;
this.max = max;
}
public long getValue() {
return value;
}
public long getMin() {
return min;
}
public long getMax() {
return max;
}
}
We could use that to define a person class that only allows
an age of between zero and 150. For example:
public class Person {
public static final int MIN_AGE = 0;
public static final int MAX_AGE = 150;
private final int age;
private final String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
if (this.age < MIN_AGE || this.age > MAX_AGE) {
throw new OutOfRangeException(this.age, MIN_AGE, MAX_AGE);
}
}
public String toString() {
return "Person " + name + " is " + age + " years old";
}
}
Throw exceptions early
Exceptions should be thrown as early as possible. As soon as
you detect the error condition, the exception needs to be
generated. If you wait for too long, it becomes more
difficult to analyze the problem.
Let's take as a counter-example the
ConcurrentModificationException. A thread is
happily iterating through a collection, when it encounters
this exception. The thread that was iterating did not cause
the exception; it was caused by another thread modifying the
collection concurrently. In order to fix the problem, we need
to consider all the places in our code that are modifying the
collection. It is usually easier to change the collection to
be threadsafe, such as
CopyOnWriteArrayList,
rather than to try to fix the actual problem. Another
alternative is to synchronize around the entire iteration,
but that comes at a great cost to concurrency.
Here we see two threads, one is reading from a collection,
the other is modifying it. The reader thread dies with a
ConcurrentModificationException, whereas the writer happily
carries on:
import java.util.*;
public class ConcurrentExceptionTest {
private static volatile boolean running = true;
public static void main(String[] args)
throws InterruptedException {
final Collection shared = new ArrayList();
Thread reader = new Thread("Reader") {
public void run() {
while (running) {
// ConcurrentModificationException happens here
for (Object o : shared) {
}
}
}
};
reader.start();
Thread writer = new Thread("Writer") {
public void run() {
while (running) {
// the thread modifying the collection does
// not see any exception
shared.add("Hello");
shared.remove("Hello");
}
}
};
writer.start();
Thread.sleep(2000);
System.out.println("reader alive? " + reader.isAlive());
System.out.println("writer alive? " + writer.isAlive());
running = false;
}
}
This particular article is about exceptions, rather than
thread safety. However, here is one (of many) solutions that
fixes the problem without reducing concurrency on the read:
We defined a shared ReadWriteLock called lock. The reading
thread then changes to:
while (running) {
lock.readLock().lock();
try {
for (Object o : shared) {
}
} finally {
lock.readLock().unlock();
}
}
The writing thread changes to:
while (running) {
lock.writeLock().lock();
try {
shared.add("Hello");
shared.remove("Hello");
} finally {
lock.writeLock().unlock();
}
}
Catch exceptions late
Similarly, exceptions should be caught in those contexts
where some corrective action can be taken. Otherwise, pass
them up the hierarchy until you have enough information so
that you can effectively deal with them. Thus we can say
"throw exceptions early" and "catch exceptions late". We need
to defined our interfaces carefully to allow future users to
throw exceptions from methods.
Document exceptions
Methods and constructors that might throw exceptions should
be clearly documented, including hints for the user as to
what exceptions can be thrown under given circumstances. This
makes it easier for clients to deal with the exceptions
correctly.
I usually include both unchecked and checked exception
comments. The unchecked exceptions are actually more
important to document, since you cannot easily figure out
what might be thrown by looking at the method signature.
For example, here is my Person constructor with JavaDoc
comments:
/**
* Constructs a person with the given age and name.
*
* @param age The age must fit into a range, specified by
* MIN_AGE and MAX_AGE
* @param name The name should not be null, nor an empty
* string
* @throws OutOfRangeException if the age is out of range.
* The age may not be less
* than the constant MIN_AGE
* and may not be more than
* the constant MAX_AGE.
* @throws IllegalArgumentException if the name is null or
* empty.
* @see #MIN_AGE, #MAX_AGE
*/
public Person(int age, String name) {
this.age = age;
this.name = name;
if (this.age < MIN_AGE || this.age > MAX_AGE) {
throw new OutOfRangeException(this.age, MIN_AGE, MAX_AGE);
}
if (this.name == null || this.name.equals("")) {
throw new IllegalArgumentException(
"name parameter should not be null nor empty");
}
}
Unit Test Exceptions
When writing code that may throw exceptions, it is a good
idea to also unit test them. The unit tests should be written
both for the producer and the consumer of the exception. For
example, for the Person class, we could write the following
unit test:
import junit.framework.TestCase;
public class PersonTest extends TestCase {
public void testExceptions() {
new Person(36, "Heinz");
new Person(Person.MIN_AGE, "Heinz");
new Person(Person.MAX_AGE, "Heinz");
try {
new Person(Person.MIN_AGE - 1, "Heinz");
fail("Allowed setting of out of range age");
} catch (OutOfRangeException success) {
}
try {
new Person(Person.MAX_AGE + 1, "Heinz");
fail("Allowed setting of out of range age");
} catch (OutOfRangeException success) {
}
try {
new Person(Integer.MAX_VALUE, "Heinz");
fail("Allowed setting of out of range age");
} catch (OutOfRangeException success) {
}
try {
new Person(Integer.MIN_VALUE, "Heinz");
fail("Allowed setting of out of range age");
} catch (OutOfRangeException success) {
}
}
}
In JUnit 4, it is a bit easier to test whether exceptions
occur in our code. Here is the same unit test, but this time
with JUnit 4 (thanks to Al Scherer for pointing this out):
import org.junit.Test;
public class PersonTestJUnit4 {
@Test
public void correctAges() {
new Person(36, "Heinz");
new Person(Person.MIN_AGE, "Heinz");
new Person(Person.MAX_AGE, "Heinz");
}
@Test(expected = OutOfRangeException.class)
public void tooYoung() {
new Person(Person.MIN_AGE - 1, "Heinz");
}
@Test(expected = OutOfRangeException.class)
public void tooOld() {
new Person(Person.MAX_AGE + 1, "Heinz");
}
@Test(expected = OutOfRangeException.class)
public void muchTooOld() {
new Person(Integer.MAX_VALUE, "Heinz");
}
@Test(expected = OutOfRangeException.class)
public void muchTooYoung() {
new Person(Integer.MIN_VALUE, "Heinz");
}
}
It is not always easy to test the code that needs to catch
the exceptions, since the framework might not cause them at
the appropriate time. For example, how do you simulate a
FileNotFoundException? Would you let your unit test remove
or rename the file? That might work, but perhaps this might
be a file that is critical to your system, then removing it
would not be an option. A better approach, though not
always possible, is to replace the real objects with mock
objects.
Assertions
Assertions were introduced in Java 1.4 as an alternative to
plain exception handling. However, instead of adding a
completely new mechanism, they patched it onto the current
exception mechanism. This has several implications for how
we can use them in our code.
Error blasts through catch(Exception)
When an assertion fails, an AssertionError is thrown. Since
this is a subclass of Error, it will blast through our
standard exception handling code and usually kill our thread.
Remember that Error is an unchecked exception.
The failed assertion would not cause the system to exit
completely, but just the thread that caused it. You can
actually catch AssertionError and ignore the error, but of
course there is no good reason for doing this.
public class FailedAssertion {
public static void main(String[] args) {
try {
assert 4 == 5 : "4 is not 5, we thought it was";
} catch (AssertionError ae) {
System.out.println("We are ignoring this: " + ae);
}
System.out.println("The main thread happily carries on ...");
}
}
When we run this with -enableassertions (or -ea for short),
we see the following output:
We are ignoring this: AssertionError: 4 is not 5, we thought it was
The main thread happily carries on ...
Can be turned on/off per class
Assertions in C need to be set at compile time. In Java they
can be enabled and disabled on a per-class basis at startup.
This in effect increases the possible state space of our
program exponentially. Every time we encounter an assertion,
the program could go in two directions: either the assertion
is evaluated, which might result in an AssertionError or it
is not evaluated, in which case the thread simply carries on.
Every time a class contains an assertion, our state space
doubles. Thus, if we have 20 classes with assertions, we
would have 2^20 = 1 million times more states. To completely
test the system, we would need to enable and disable the
assertion status of each of the classes in the system.
In practice, we usually enable either all or none of the
assertions. We would thus only double the state space, so we
need to qualify our system with assertions on and then redo
the qualification with them turned off.
Pragmatic Programmer
In the book Pragmatic
Programmer, the authors make a strong point for
keeping assertions turned on all the time, even in
production. Since they could be turned off at runtime, we
need to assume that they may not be evaluated at all.
Instead of using the assert
mechanism, I sometimes throw the AssertionError directly,
thus removing any confusion:
import java.util.*;
import java.util.concurrent.*;
public class FailedAssertion2 {
public static <T> List<T> makeRandomList() {
switch ((int) (Math.random() * 3)) {
case 0: return new ArrayList<T>();
case 1: return new LinkedList<T>();
case 2: return new CopyOnWriteArrayList<T>();
default: throw new AssertionError("Impossible case");
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(
makeRandomList().getClass().getSimpleName());
}
}
}
Exception Anti-Patterns
No discussion on exceptions would be complete without
considering anti-patterns that have been adopted by
programmers.
Flow control
We should never cause an exception that is otherwise
preventable. I have seen code where instead of checking
bounds, it is assumed that the data will be correct and
then RuntimeExceptions are caught:
Here is an example of bad code (please don't code like this):
public class Antipattern1 {
public static void main(String[] args) {
try {
int i = 0;
while (true) {
System.out.println(args[i++]);
}
} catch (ArrayIndexOutOfBoundsException e) {
// we are done
}
}
}
Avoiding cost of if
The second anti-pattern started appearing as a misguided
attempt to optimize Java code. The argument was that the
try-catch statement costs less than
the if when the condition is mostly
true anyway. The code then became completely unreadable, due
to deep levels of try-catch. Again, this is an anti-pattern,
so I do not recommended that you implement your code this
way:
public class Antipattern2 {
private final int val1;
private final String val2;
private final long val3;
public Antipattern2(int val1, String val2, long val3) {
this.val1 = val1;
this.val2 = val2;
this.val3 = val3;
}
public boolean equals(Object o) {
if (this == o) return true;
try {
Antipattern2 that = (Antipattern2) o;
return val1 == that.val1
&& val3 == that.val3
&& (val2 == that.val2 || val2.equals(that.val2));
} catch(ClassCastException ex) {
return false;
} catch(NullPointerException ex) {
return false;
}
}
public int hashCode() {
int result;
result = val1;
result = 31 * result + val2.hashCode();
result = 31 * result + (int) (val3 ^ (val3 >>> 32));
return result;
}
}
}
Conclusion
We started off this article with some historical discussion
that explained where the name RuntimeException comes from. We
then looked at some best practices for using exceptions
correctly, followed by a brief look at assertions. Lastly, we
ended off with two anti-patterns for how programmers abuse
exception handling.
Kind regards
Heinz
Exceptions Articles
Related Java Course
|