The exception transparency is an interesting concept explained by Brian Goetz in http://blogs.sun.com/briangoetz/entry/exception_transparency_in_java. The idea is actually not specifically related to closure, but the need to have it becomes clear with inclusion of closure in Java.
As clear from previous posts (e.g. https://sites.google.com/site/anrizal05/home/java/007---hacking-closure-in-jdk-7-0), the use of SAM is a key in closure. The challenge is, of course, to have an interface that promotes reusability and of course simplity of use. One of the problem in designing such interface is the type of exception to be thrown.
For example, if we want to define an interface Block with run method that throws BlockExecutionException like the following:
public interface Block<T> {
public void run(T) throws BlockExecutionException;
}
That looks fine. Now imagine if we want to have an implementation of Block that throws SubBlockExecution1Exception and SubBlockExecution2Exception (both extends BlockExecutionException) like this:
public class BlockImpl<Integer> implements Block{
public void run(Integer) throws SubBlockExecution1Exception , SubBlockExecution2Exception {
...
}
}
And imagine that the code works directly with BlockImpl like this:
BlockImpl blockImpl = new BlockImpl();
blockImpl.run();
We need to catch SubBlockExecution1Exception and SubBlockExecution2Exception thrown by BlockImpl.run, which may be great, since both are more specific exceptions than BlockExecutionException.
If we work with Block:
Block<Integer> blockImpl = new BlockImpl();
blockImpl.run();
We're still able to catch SubBlockExecution1Exception and SubBlockExecution2Exception thrown by Block.run, but compiler still requires us to catch BlockExecutionException.
Block<Integer> block = new BlockImpl();
try {
block.run();
}
catch (SubBlockExecution1Exception e) {
handleSubBlockExecution1Exception(e);
}
catch (SubBlockExecution2Exception e) {
handleSubBlockExecution2Exception(e);
}
catch (BlockExecutionException e) {
// still needed.
}
If we go further to apply such problematic in closure, this becomes clearer. Imagine if we call the method foreach like the following:
foreach( myList, #() { method1(); method2(); } )
With the definition of foreach as follow:
void foreach( List<Integer> list, Block<T> block) {
for (i : list) {
block.run();
}
}
And method1 and method2 as follow:
void method1() throws SubBlockExecution1Exception;
void method2() throws SubBlockExecution2Exception;
Because of the definition of Block<T>, we cannot catch only SubBlockExecution1Exception and SubBlockExecution2Exception, but instead we need also to catch BlockExecutionException. This is not great since this will encourage application to directly catch BlockExecutionException instead of handling the more specific exception. In closure, it is even worse since we need to catch exception specified in the SAM, while we don't even see the SAM code.
Because of the definition of Block<T>, we cannot catch only SubBlockExecution1Exception and SubBlockExecution2Exception, but instead we need also to catch BlockExecutionException. This is not great since this will encourage application to directly catch BlockExecutionException instead of handling the more specific exception. In closure, it is even worse since we need to catch exception specified in the SAM, while we don't even see the SAM code.
So, what the proposal is about ?
The proposal is about the possibility to generify exception like follow:
interface Block<T throws E> {
void run(T item) throws E;
}
or
interface Block<T throws E extends BlockExecutionException> {
void run(T item) throws E;
}
Basically, exception transparency allows us to specify throws in variable type. Then, we can have the following code:
private void execute(String x) throws SubBlockExecutionException {
and we add foreach to ListUtil class as follow:
We're ready then to write the code that shows the use of exception transparency:
System.out.println("Execute" + x);
...
}
and we add foreach to ListUtil class as follow:
public static <T,E extends BlockExecutionException>
void foreach(List<T> list, Block<T, E> block) throws E{
for (T item : list) {
block.run(item);
}
}
We're ready then to write the code that shows the use of exception transparency:
List<String> myList = Arrays.<String>asList("A", "B", "K");
try {
ListUtil.foreach(
myList,
Block<String, SubBlockExecutionException> #(x) {execute(x)});
}
catch (SubBlockExecutionException e) {
e.printStackTrace();
}
We don't need to catch BlockExecutionException because we know that execute method throws SubBlockException instead. Hence the transparency.
It is pretty cool, except the need to explicitly specify the target type. I asked this to lambda-dev mailing list and the response is that the type inference would only be done on the enviroment of the execution of closure not the content of the closure itself. So, the type inference does not check that the closure actually invokes execute( ) that throws SubBlockExecutionException. One solution would be to infer using the content of the closure or to live with that solution, which means that the exception is not really transparent :-)
But...., somehow, the following code works (that is, the type of lambda parameter is provided). According to Maurizio Cimadamore this is due to circular inference. Hmm... I hope to be informed more on what circular inference is.
List<String> myList = Arrays.<String>asList("A", "B", "K");
try {
ListUtil.foreach(
myList,
#(String x) {execute(x)});
}
catch (SubBlockExecutionException e) {
e.printStackTrace();
}
No comments:
Post a Comment