Inferred exceptions in Java
It’s always nice to borrow and steal concepts and ideas from other languages. Scala’s Option is one idea I really like, so I wrote an implementation in Java. It wraps an object which may or may not be null, and provides some methods to work with in a more kinda-sorta functional way. For example, the isDefined method adds an object-oriented way of checking if a value is null. It is then used in other places, such as the getOrElse method, which basically says “give me what you’re wrapping, or a fallback if it’s null”.
public T getOrElse(T fallback) { return isDefined() ? get() : fallback; }
In practice, this would replace tradional Java, such as
public void foo() { String s = dao.getValue(); if (s == null) { s = 'bar'; } System.out.println(s); }
with the more concise and OO
public void foo() { Option<String> s = dao.getValue(); System.out.println(s.getOrElse('bar')); }
However, what if I want to do something other than get a fallback value – say, throw an exception? More to the point, what if I want to throw a specific type of exception – that is, both specific in use and not hard-coded into Option? This requires a spot of cunning, and a splash of type inference.
Because this is Java, we can start with a new factory – ExceptionFactory. This is a basic implementation that only creates exceptions constructed with a message, but you can of course expand the code as required.
public interface ExceptionFactory <E extends Exception> { E create(String message); }
Notice the <E extends Exception> – this is the key to how this works. Using the factory, we can now add a new method to Option:
public <E extends Exception> T getOrThrow(ExceptionFactory<E> exceptionFactory, String message) throws E { if (isDefined()) { return get(); } else { throw exceptionFactory.create(message); } }
Again, notice the throws E – this is inferred from the exception factory.
And that, believe it or not, is 90% of what it takes. The one irritation is the need to have exception factories. If you can stomach this, you’re all set. Let’s define a couple of custom exceptions to see this in action.
public <E extends Exception> T getOrThrow(ExceptionFactory<E> exceptionFactory, String message) throws E { if (isDefined()) { return get(); } else { throw exceptionFactory.create(message); } }
And the suspiciously similar ExceptionB
public class ExceptionB extends Exception { public ExceptionB(String message) { super(message); } public static ExceptionFactory<ExceptionB> factory() { return new ExceptionFactory<ExceptionB>() { @Override public ExceptionB create(String message) { return new ExceptionB(message); } }; } }
And finally, throw it all together:
public class GenericExceptionTest { @Test(expected = ExceptionA.class) public void exceptionA_throw() throws ExceptionA { Option.option(null).getOrThrow(ExceptionA.factory(), "Some message pertinent to the situation"); } @Test public void exceptionA_noThrow() throws ExceptionA { String s = Option.option("foo").getOrThrow(ExceptionA.factory(), "Some message pertinent to the situation"); Assert.assertEquals("foo", s); } @Test(expected = ExceptionB.class) public void exceptionB_throw() throws ExceptionB { Option.option(null).getOrThrow(ExceptionB.factory(), "Some message pertinent to the situation"); } @Test public void exceptionB_noThrow() throws ExceptionB { String s = Option.option("foo").getOrThrow(ExceptionB.factory(), "Some message pertinent to the situation"); Assert.assertEquals("foo", s); } }
The important thing to notice, as highlighted in bold above, is the exception declared in the method signature is specific – it’s not a common ancestor (Exception or Throwable). This means you can now use Options in your DAO layer, your service layer, wherever, and throw specific exceptions where and how you need.
Download source: You can get the source code and tests from here – genex
Sidenote
One other interesting thing that came out of writing this was the observation that it’s possible to do this:
public void foo() { throw null; } public void bar() { try { foo(); } catch (NullPointerException e) { ... } }
It goes without saying that this is not a good idea.
Reference: Inferred exceptions in Java from our JCG partner Steve Chaloner at the Objectify blog.
Why do you need a factory what is wrong with getOrThrow(RuntimeException e) {
e.fillInStackTrace();
throw e;
}
This provides a way of specifying checked exceptions that can be specified at call-time.
Yes I follow. Maybe I should have made a more complete example.
public static class Usage {
public void example(){
Optional optional = new Optional();
try {
optional.getOrThrow(new IOException(“SomeReason”));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static class Optional {
public T getOrThrow(E e) throws E {
if (isDefined()) {
return get();
} else {
e.fillInStackTrace();
throw e;
}
}
.. other methods
}
I still don’t understand. Do you mean, a) why use a factory, or b) why not just use runtime exceptions? It’s not clear from the example, so I’ll answer both. A. Using a factory means you don’t have to create the exceptions if you don’t need them. I think that exceptions should only be created if you’re going to throw them (personal coding style). Using a factory also allows flexibility in how it is created, e.g. 1, pass in a new factory to each call to getOrThrow 2. Use a static factory to save creation overhead 3. Inject with DI… Read more »
I believe what he is saying is you can create the exception and pass it in, just as with getOrElse(). The factory seems to be unnecessary.
That’s also what I understand about his point, but it misses an important point. If you pass in a pre-instantiated exception, you can’t give it a cause later and so you lose stack information.