Tired of dealing with checked exceptions?
These people are.
Compile-time checking of exceptions is a useful language feature, but
often this feature can become a nuisance. This is particularly true
when checked exceptions are used (in my opinion) inappropriately, for example to
signify an unavailable resource or a coding error.
When calling a method that throws a checked exception, you have a few
options. You can catch the exception and handle it yourself; however,
this is not always possible. You can declare that you also
throw the checked exception, but this can lead to a proliferation
of throws
declarations as exceptions need to propagate
farther and farther up the stack. Many exceptions are best handled at the
top level of the stack, or even outside of the stack in an
UncaughtExceptionHandler
, and it doesn’t take many checked
exceptions before this process can become untenable.
Another popular option is to convert checked exceptions into unchecked exceptions
by using a wrapper exception. For example:
public Object loadObject (int objId)
{
try {
Connection c = Database.getConnection();
PreparedStatement query = conn.prepareStatement(OBJECT_QUERY);
query.setInt(1, objId);
ResultSet rs = query.executeQuery();
...
} catch (SQLException ex) {
throw new RuntimeException("Cannot query object " + objId, ex);
}
}
Chaining exceptions in this manner has a few advantages.
Exception.printStackTrace()
will print the entire chain of stack
traces, so no information is lost in the wrapping process. Each
exception can also annotate the stack trace with additional debugging
information (for example, by appending the object identifier in the
above example).
There is one major disadvantage, though. We’ve preserved the
debugging information contained in the original exception, but we’ve
lost the ability to easily trap exceptions based on this information.
For example, let’s assume that the following method is near the top of
the stack. This will no longer work, as the exception thrown is now
a RuntimeException
rather than an SQLException
.
public DataSet loadData ()
{
for (Connection c : getAllConnections()) {
Database.setConnection(c);
try {
return attemptToLoadData();
} catch (SQLException ex) {
logException(ex);
}
}
throw new PersistenceException("cannot load data");
}
However, there is a fourth option. We can actually throw a checked
exception at run-time without either the compiler or the bytecode
verifier knowing about it. The public class sun.misc.Unsafe
provides a wide variety of “dangerous” utility functions, including
direct memory access (which can be used to simulate pointer arithmetic)
and object access which is much faster than reflection. In subsequent
posts I will discuss many of these other features, but in this post I am
focusing on the throwException
method.
public void someMethod () // no "throws" clause!
{
getUnsafe().throwException(new Exception("test"));
}
The Unsafe
class and throwException
method are both public, so
any security surrounding this class are based entirely on whether you can
obtain a instance of it.
Code that is distributed along with Java can simply call the static factory
method Unsafe.getUnsafe()
, but we’re not quite so lucky. This
method verifies that the calling class was loaded through the bootstrap
ClassLoader
, so we would have to modify the -Xbootclasspath
for this to work for our own code. However, there is another option — if
the current SecurityManager
allows it, we can override the accessibility
of the static field that stores the Unsafe
instance and retrieve it
directly.
public Unsafe getUnsafe ()
{
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe)field.get(null);
} catch (Exception ex) {
throw new RuntimeException("can't get Unsafe instance", ex);
}
}
“Why does such a method even exist?”, you may be asking yourself. Surely
Sun wouldn’t throw any checked exceptions from a method that doesn’t
declare those exceptions, right? That’s cheating!
Well, in fact, they do — in Class.newInstance()
. If you get
a java.lang.reflect.Constructor
and call newInstance()
on it, you will receive one
of three exceptions: IllegalArgumentException
,
IllegalAccessException
,
or InvocationTargetException
. The first two exceptions are caused by
the act of invoking a method through reflection, but the third is used
to wrap any other exceptions which are thrown from within the constructor.
However, Class.newInstance()
behaves differently; it will not throw an
InvocationTargetException
. It may throw an IllegalArgumentException
or IllegalAccessException
, but if
any exceptions are thrown from the constructor they will be thrown directly,
rather than being wrapped with an InvocationTargetException
.
In fact, if you look at the code for Class.newInstance()
, you’ll see this:
private Object newInstance0()
throws InstantiationException, IllegalAccessException
{
...
// Run constructor
try {
return tmpConstructor.newInstance(null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
I’m not necessarily advocating the use of this method to avoid compile-time
checking of exceptions, but I think it’s important to know that you have
this option. It’s a bit of a hack, but hey — if it’s good enough for Sun
it must be good enough for us, right?
There is one place in particular where I can see this method being useful.
Some Aspect Oriented Programming (AOP) frameworks, such as AspectJ, have
support for automatically “softening” exceptions by wrapping them with an
unchecked exception. However, this suffers from the same problem that
we highlighted above — the identity of the exception changes after it
is wrapped, and therefore it is more difficult to catch the exception at
a higher level. It may be interesting to give these frameworks
support for using Unsafe.throwException
instead.
Another interesting idea is to implement an annotation that softens
exceptions in this manner. Using either source-code instrumentation
(via apt
) or bytecode instrumentation (as I described
in my
Peeking Inside the Box
articles), code could be inserted that would use
Unsafe.throwException
to soften the exceptions. Unlike the AOP
approach, this would make it clear from the source code what is happening.
@Softens{SQLException.class}
public void loadData (int objId)
{
// ...code that throws an SQLException...
}
Have checked exceptions done more harm than good in your development career? If so, how do you deal with it?