|
The Java Specialists' Newsletter
Issue 089 2004-05-26
Category:
Exceptions
Java version: Sun JDK 1.5.0-beta Catching Uncaught Exceptions in JDK 1.5by Dr. Heinz M. Kabutz
Welcome to the 89th edition of The Java(tm) Specialists' Newsletter. We have probably the
most elite Java newsletter in the world, so if you are a
member, you are a part of that elite! If you know of people
who are really good at Java, please let them know about this
newsletter.
Today was an exceptionally beautiful day in Cape Town. No
wind, nice warm weather, as if it were summer. My friend
Herman Lintvelt (who has authored a few newsletters) and I had a
great lunch dining on huge slabs of meat and fine South African
red wine, to celebrate the "good life". I am sitting
outside on my balcony at 23:00 enjoying a mild evening at
25 degrees celsius :-) It will probably rain tomorrow.
A small change in my newsletter structure is that from now on,
the heading will show which version of Java I was working
with when I wrote the newsletter. Since The Java(tm) Specialists' Newsletter explores
interesting features, we sometimes stumble across "features"
that are actually bugs and that are removed in the next
release. This has caused confusion in the past, especially
when readers look at older newsletters.
I would like to thank all those who sent me their quotes of
what they thought about the newsletter. I was touched, and
have new motivation and energy to write these newsletters :-)
Last week I presented a Java
Course at a South African company. During the course,
one of the C++ programmers questioned me about generics in
Java. I try to stay away from beta versions for production
code, but curiosity got the better of me, so I tried playing
with it. IntelliJ IDEA 4.0 was not too happy with the new
for construct, so I tried IDEA 4.1,
which worked fine.
I must admit that generics take some getting used to, and
changing your code is not always straightforward. For example,
I could not find a way of using generics in a static context.
In a future newsletter, I will write about some of the
experiences of migrating my existing code to generics.
A Google on "generics java" gave me approximately 50'000 hits,
so I won't bore you with "yet another how-to-do Java Generics
newsletter" until I have something interesting to write about them :-)
Instead, like most topics in this newsletter series, I will
write about something that I discovered by chance, whilst
I was glancing at the source code of Sun JDK 1.5 beta. Google
did not reveal any newsletters about this topic, so here goes...
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. Catching Uncaught Exceptions in JDK 1.5
In my experience, all Java projects contain poorly written
exception handling code. Let's take a simple example, and
make it complicated:
import java.sql.*;
import java.util.List;
/**
* You'll have to compile with JDK 1.5 and use the switch
* javac -source 1.5
*/
public class DatabaseQueryCode {
private final Connection con;
public DatabaseQueryCode(Connection con) {
this.con = con;
}
/**
* Take a list of Strings and execute all the queries in one
* transaction.
*/
public void executeQueries(List<String> queries) throws SQLException {
con.setAutoCommit(false);
Statement st = con.createStatement();
for(String s : queries) { // I love this construct :-)
st.execute(s);
}
con.commit();
st.close();
con.setAutoCommit(true);
}
}
That code is obviously not as correct as it could have been.
If we fail halfway through the method, we won't set the
auto-commit to be true, so let's change that:
public void executeQueries(List<String> queries) throws SQLException {
con.setAutoCommit(false);
Statement st = con.createStatement();
try {
for(String s : queries) {
st.execute(s);
}
con.commit();
st.close();
} finally {
con.setAutoCommit(true);
}
}
This is better, but also not ideal. If any of the queries
fail, we want to roll back and still close the statement, and
we want to make sure that we do not close the statement if
it was not open, so let's change it again.
public void executeQueries(List<String> queries) throws SQLException {
con.setAutoCommit(false);
Statement st = con.createStatement();
try {
for(String s : queries) {
st.execute(s);
}
con.commit();
} catch(SQLException ex) {
con.rollback();
} finally {
st.close();
con.setAutoCommit(true);
}
}
Good, this is better, but what happens if one of the Strings
is null and we get a NullPointerException?
What happens if we run out of memory and get an
OutOfMemoryError? What happens if we get an
OOME and at the same time the connection does not work
anymore? Then the finally would cause an exception, which
would mask the OOME, and make it disappear. There are lots
of possibilities, and if we try to cater for all
eventualities (excuse the pun) then we will go crazy trying
and our code will look rather complicated.
My point with this example was not to show you how to write
the perfect database exception handling. Truth is, I don't
know how to make it bullet proof. Maybe water balloon proof,
but not bullet proof.
So in the real world, how are exceptions handled?
Frequently, exceptions are stubbed out and ignored, because
the writer of the code did not know how to handle the error
(and was going to go back and fix it, one day, but the
project manager was breathing down his neck and the release
had to go out that afternoon). This is bad, since you then
do not know that something has gone awry. On the other hand,
if the exception bubbles up the call stack, it may kill the
thread, and you may never know that there was an exception.
I have witnessed production code do things like this (I kid
you not):
try {
// do something
} catch(Exception ex) {
// log to some obscure log file, maybe
return "";
}
The effect was that the webpage showed empty strings as
values when something went wrong with the code.
My approach to exceptions is to have a central mechanism that
deals with any exceptions that I am not 100% sure of
how to handle. Whenever something goes wrong, this central
place is notified. However, what happens when you are using
someone else's code and their threads die without warning?
An amusing example was an early version of Together/J. I enjoyed
using Together/J, even though it was rather memory
hungry. Instead of starting with 512MB as default maximum old
generation memory size, I set it to only use 92MB. This
made Together work faster and save resources. However,
occasionally random threads would simply die, so you could
perhaps not print anymore or some other functionality would
vanish.
How does JDK 1.5 help?
In newsletter 81,
I described a way that you could catch unhandled exceptions in
your GUI code, by starting up your GUI in a special thread
group. I had assumed that this was the way that uncaught
exceptions should be handled in future. The old way of
catching these exceptions was to set a system property, but
in the JDK code comments that was described as a temporary
workaround.
If you look at the java.lang.Thread
JavaDocs, you will notice some new methods that can help us,
specifically setDefaultUncaughtExceptionHandler()
and
setUncaughtExceptionHandler().
With these two methods, you can specify an exception handler
for an individual thread (setUncaughtExceptionHandler()) or
you can set a default handler for all threads that do not
have their own UncaughtExceptionHandler (setDefaultUncaughtExceptionHandler()).
To contrast this with the earlier newsletter, please use the
Gui class of newsletter
81 and compile it together with these two classes,
DefaultExceptionHandler and EvenBetterGui:
import javax.swing.*;
import java.awt.*;
// did you know that you could import inner classes?
import java.lang.Thread.*;
public class DefaultExceptionHandler implements UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
// Here you should have a more robust, permanent record of problems
JOptionPane.showMessageDialog(findActiveFrame(),
e.toString(), "Exception Occurred", JOptionPane.OK_OPTION);
e.printStackTrace();
}
private Frame findActiveFrame() {
Frame[] frames = JFrame.getFrames();
for (int i = 0; i < frames.length; i++) {
if (frames[i].isVisible()) {
return frames[i];
}
}
return null;
}
}
import javax.swing.*;
public class EvenBetterGui {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(
new DefaultExceptionHandler());
Gui gui = new Gui();
gui.pack();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setVisible(true);
}
}
We can now catch all unhandled exceptions by calling
Thread.setDefaultUncaughtExceptionHandler()
and passing in our own exception handler (subject to security
manager permissions of course).
In my opinion, this is a great addition to the Java Programming
Language, and I am looking forward to finding more nuggets
that will convince me to switch over to JDK 1.5 permanently.
A nice resource for finding differences between JDK 1.4.2 and
JDK 1.5 is JDiff. [I discovered after sending this newsletter
that the author of JDiff is on our newsletter :-]
Thread has some other rather useful methods, such as
getStackTrace() and getAllStackTraces().
What else can you do in JDK 1.5? You can measure elapsed
time in nanoseconds instead of milliseconds, which should
make performance calculations more accurate (or more suspect?).
Have a look at System.nanoTime().
That's all for this newsletter. I have to get to sleep
before I catch yet another cold from overworking...
Kind regards
Heinz
Exceptions Articles
Related Java Course
|