Let's try .. throws .. catch .. finally
I'm not the first who's going to be suggesting a different approach to the problem checked exception causes: Boiler-plate code.
Let's sum it up: Checked exceptions mean that we need to either catch an exception thrown by a method call, or declare that the method we're implementing throws that exception, unless the exception is Adding a throws rethrown exception instance for exception type list : caught exception instance type caught exception instance name clause directly after the
Adding a catch caught exception type list : caught exception instance type caught exception instance name clause directly after the previous point's clause and before the normal
So, the previous example will be coded to be:
RuntimeException or a subclass of it.
This means that, when calling File.createTempFile, we can either write:
public void myMethod() throws IOException {
File f = File.createTempFile("pre", "suf");
}
Or:
public void myMethod() {
try {
File f = File.createTempFile("pre", "suf");
} catch (IOException e) {
// do something with e.
}
}
When part of a larger application, you won't just declare the exception because it is considered an ugly design: It reduces abstraction by revealing part of your lower-level, internal implementation. That's why what's usually done with exceptions is to either log it, or wrap it in an API specific exception (such as a JAXBException wrapping IOException).
However, that tends to become boiler-plate code. For example, the following code:
Class clz = ...
try {
a = clz.getConstructor(Integer.TYPE).newInstance(5);
} catch (InstantiationException e) {
throw new MyException(e);
} catch (IllegalAccessException e) {
Logger.log(e);
} catch (IllegalArgumentException e) {
throw new MyException(e);
} catch (InvocationTargetException e) {
throw new MyException(e);
} catch (NoSuchElementException e) {
throw new MyException(e);
} catch (SecurityException e) {
Logger.log(e);
}
I mean... Just look at it! And you see that everywhere. And worse, a lot of people start to rethrow RuntimeException to rid themselves from "the problem". Checked exceptions aren't be a problem: they are an extremely helpful tool, aimed to help developers have another layer in their interface and module design. The problem we have is the syntax offered, or rather, the syntax lacking to deal with many exceptions and the aforementioned boiler-plate code.
As I said, lately it's a fashion to suggest your own syntax as a solution, so I'm adding my two cents, and suggest the following:
try clause and before other catch clauses; this will rethrow the exception for any exception of the types mentioned.catch clause; this will catch all exceptions of the specified types.
Class clz = ...
try {
a = clz.getConstructor(Integer.TYPE).newInstance(5);
} throws new MyException(e) for InstantiationException,
NoSuchElementException, InvocationTargetException,
IllegalArgumentException : Exception e;
catch (IllegalAccessException, SecurityException : Exception e) {
Logger.log(e);
}
The previous code would obviously unfold to the original code - This is "syntax sugar", but one that is much needed!
I'd like to hear your opinion, and of other ways this could have been done better - Please comment!
Comments (9)
Class clz = ... try { a = clz.getConstructor(Integer.TYPE).newInstance(5); } catch (InstantiationException e) { throw new MyException(e); } catch (IllegalAccessException e) { Logger.log(e); } catch (IllegalArgumentException e) { throw new MyException(e); } catch (InvocationTargetException e) { throw new MyException(e); } catch (NoSuchElementException e) { throw new MyException(e); } catch (SecurityException e) { Logger.log(e); }Can be transformed to:Class clz = ... try { a = clz.getConstructor(Integer.TYPE).newInstance(5); } catch (SecurityException e) { Logger.log(e); }catch (IllegalAccessException e) { Logger.log(e); } catch (Exception e) { throw new MyException(e); }Assumption: The caught exceptions are the only checked exceptions thrown by the funciton called.try { /* something */ } catch ( IOException, NumberFormatException e ) {}There is no need to explicitly specify how you'll be treating e; it's the common ancestor. In case there are multiple common ancestors (only happends if exceptions start implementing custom interfaces which rarely happends), ignore the interface part of the hierarchy, or, alternatively, turn e into a joint type. Java does support this 'under the hood' since java 1.5. (Generics parameters can have joint types, and the tertiary operator can return joint types - the idea that the type of the expression is A && B - it is guaranteed to be both so you can assign it to a variable of either type. In regards to the rethrows form, I don't like it. I don't think it's needed either - once this multi-list thing is possible, you can roll your own in 3 lines which is hardly worse than the special syntax. here's your complete example, rewritten the way I'd implement this stuff:Class clz = ... try { a = clz.getConstructor(Integer.TYPE).newInstance(5); } catch ( InstantiationException, NoSuchElementException, InvocationTargetException, IllegalArgumentException e ) { throw new MyException(e); } catch ( IllegalAccessException, SecurityException e ) { Logger.log(e); }It adheres to all the quality standards for a language change: 1. it does not conflict with any existing code; there is no code that compiles today that would do something different/no longer compiles with this change adapted. 2. it does not create binary or source incompatibilities; this is just syntactic sugar that gets compiled the same way as writing each catch block separately. 3. it is intuitive and simple to understand. 4. it does not conflict with the current parser strategy. 5. it solves a useful problem that has been observed in the wild for many years and does not appear to be going away anytime soon.