| |||||||||
Exception handling problems in Java, Part I
Exception handling problems in Java, Part IThis article constitutes the part I of a two-part series. Part II is the Exception handling problems in Java, part II There is something wrong in Java land. Simply put we need better exception throwing behaviour detection in the transitive closure of code blocks in the Java type system. As I will show in this article, the compiler works in mysterious ways! Get ready for some Java type system wrestling! The core of the problem in laymans terms: Java does not allow you to refactor/extract common exception handling code into a method. Let's have a look at some slightly modified code from my Super CSV project that gave me the inspiration for this article. The code below shows part of the implementation supporting reading CSV files directly into objects. To do that, we need for each column on each line in the file, to invoke a setter method on some object. There are a few requirements to finding setter methods. Since Java supports method overloading, it is insufficient to just find a setter method on the object with an appropriate name. The type parameter must also be taken into consideration. Further more, since Java also has autoboxing, a class may contain eithersetAge(int age) or setAge(Integer age) but still respond to the call obj.setAge(2). Hence when looking up a set method,
we may require two lookups (one for each of the two possible setAge()). While the folllowing code example may be a bit long, I have chosen
a real life example over some contrived made-up bullshit example. You may argue, that the behaviour when
trapping a security exception is wrong, I have no problem with that, just mail me a better example.. there are plenty to pick from!
The following example is a lookup method trying to find an appropriate setter method. The autoboxingConverter is a simple map, mapping between autoboxable types
to enable the dual lookup as mentioned.
Method inspectClassForSetMethods_old(final Object destinationObject, final Class variableType, final String variableName) {
final String methodName = "set" + variableName.substring(0, 1).toUpperCase() + variableName.substring(1);
try {
return destinationObject.getClass().getMethod(methodName, variableType);
}
catch(SecurityException e) {
e.printStackTrace();
throw new RuntimeException(String.format("Can't find method '%s(%s)' in class '%s'. "
+ "Is the name correctly spelled in the NameMapping? "
+ "Have you forgot to convert the data so that a wrong set method is called?", methodName, variableType,
destinationObject.getClass().getName()), e);
}
catch(NoSuchMethodException e) {
// retry again due to autoboxing in java we need to try both cases
try {
if( autoboxingConverter.containsKey(variableType) == false ) {
e.printStackTrace();
throw new RuntimeException(String.format("Can't find method '%s(%s)' in class '%s'. "
+ "Is the name correctly spelled in the NameMapping? "
+ "Have you forgot to convert the data so that a wrong set method is called?", methodName,
variableType, destinationObject.getClass().getName()), e);
}
return destinationObject.getClass().getMethod(methodName, autoboxingConverter.get(variableType));
}
catch(SecurityException e1) {
e.printStackTrace();
throw new RuntimeException(String.format("Can't find method '%s(%s)' in class '%s'. "
+ "Is the name correctly spelled in the NameMapping? "
+ "Have you forgot to convert the data so that a wrong set method is called?", methodName,
variableType, destinationObject.getClass().getName()), e);
}
catch(NoSuchMethodException e1) {
e.printStackTrace();
throw new RuntimeException(String.format("Can't find method '%s(%s)' in class '%s'. "
+ "Is the name correctly spelled in the NameMapping? "
+ "Have you forgot to convert the data so that a wrong set method is called?", methodName,
variableType, destinationObject.getClass().getName()), e);
}
}
// return null; // compiler error "unreachable code"
Just quickly glaring at the code, there are a number of worthwhile observations to make
Method inspectClassForSetMethods(final Object destinationObject, final Class variableType, final String variableName) {
final String methodName = "set" + variableName.substring(0, 1).toUpperCase() + variableName.substring(1);
try {
return destinationObject.getClass().getMethod(methodName, variableType);
}
catch(SecurityException e) {
throwException(destinationObject, variableType, methodName, e);
}
catch(NoSuchMethodException e) {
// retry again due to autoboxing in java we need to try both cases
try {
if( autoboxingConverter.containsKey(variableType) == false ) {
throwException(destinationObject, variableType, methodName, e);
}
return destinationObject.getClass().getMethod(methodName, autoboxingConverter.get(variableType));
}
catch(SecurityException e1) {
throwException(destinationObject, variableType, methodName, e1);
}
catch(NoSuchMethodException e1) {
throwException(destinationObject, variableType, methodName, e1);
}
}
return null; // Can never be reached.. but the compiler requires it!!
}
/** Confining exception handling code. Must always throw an exception */
private final void throwException(final Object destinationObject, final Class variableType, final String methodName,
Exception e) throws RuntimeException {
e.printStackTrace();
throw new RuntimeException(String.format("Can't find method '%s(%s)' in class '%s'. "
+ "Is the name correctly spelled in the NameMapping? "
+ "Have you forgot to convert the data so that a wrong set method is called?", methodName,
variableType, destinationObject.getClass().getName()), e);
}
}
Alas it isn't. This is really a shame, as I hope you would agree with me, it much better communicate its intention of the code,
and the code duplication is reduced to a minimum. There is a problem however. The compiler does not detect that the transitive
closure (i.e. all the code) of the method throwException() always throws an exception! Hence,
it now suddenly refuses to compile the code without the silly and utterly superfluous return null; statement at the end of the code.
The error message you get if you leave it out is that the method must return something of type Method.
Our observations from the first code example shows, that the type system is able to analyze all the try-catch blocks and conclude that the
return null; in fact never will execute. However, in our second example, the compiler forces upon the programmer to insert that very line of code.
Even though the semantics of the code never changed. That return null; never will be executed.
To add confusion to our understanding of the inner workings of the Compiler, try first changing the method throwException() to
say throws Exception. Immediately each calling point of the method is highlighted as a compile problem of unhandled exception.
Good news! Suddenly the compiler is able to detect that our glamorous throwException() method is capable of throwing exceptions! Then
try changing the method inspectClassForSetMethods() to say throws Exception. This will remove the newly arisen compiler errors.
However, if then try to removing the return null; statement you'll still get an error message.
So it was all smoking mirrors.
The checked exception statement only proofed that throwException() could throw an exception not that it always does.
More problemsSo returning a null doesn't seem like a big deal. But what if the return type of the method was a simple type, sayint. What value would
your prefer to be place holder for the unnecessary return statement? It really doesn't matter if it is return 1;, return -1, ...
as the statement never is executed, but it sure is bad for readability.
The problem further complicates code generation. I once attempted at cleaning up exception handling in the
Business delegate layer of an EJB application
I was working on. I felt the pain of first not being able to extract the common functionality into a method, and further when generating the code
hand to take into account whether to return a -1 or a null. Trying to use Eclipse IDE's neat "code templates" feature certainly
wasn't able to cope with having to make such a distinction.
SolutionsI do not understand why the discussed problem exists. We can only hope to see it disappear in Java 7 or Java 8. The sooner the better! Recently, there has been many blog entries suggesting improvements for the catch-block syntax in Java, allowing multiple exceptions to be caught in the same block. Let's see an example:
} catch (Exception1 ex) {
..
throw ex;
} catch (Exception2 ex) {
..
throw ex;
}
Could be expressed as
} catch (Exception1 | Exception2 ex) {
..
throw ex;
}
Unfortunately, this suggestion is orthogonal to the problem that I'm raising. Contemplating on
the difference between my suggested solution on what should be possible in Java, and the suggestion of having multiple exception specifiers in
the catch block, I must admit I prefer my idea. The main reasons for this is that
CommentsIf you have any comments to this article, please drop me a mail at firstclassthoughts at gmail dot com please indicate if I can't publish whole or parts of your comment on the site.If you like this site consider Help spread the wordShare this post on your favorite social bookmarking sites:
The most recent contributions 20/05/09 Board Game Jungle speed / Arriba Review of the cool game "Jungle Speed" aka. "Arriba". 16/05/09 Danish Twin words "Twin words" are words that not only have multiple meanings, they must be composed next to each other in meaningful sentences. This article explores the concept of twin words. 14/05/09 English Twin words "Twin words" are words that not only have multiple meanings, they must be composed next to each other in meaningful sentences. This article explores the concept of twin words. 04/05/09 'Office space' extras Crazy stuff about the funny movie "Office space". Nothing of interest? Try browsing the entire article archive... | |||||||||