Das Leben wäre schöner, gäbe es nur RuntimeExceptions – leider aber gibt’s auch Exceptions.
Eine übliche Lösung dieses Problems besteht bekanntlich darin, checked Exceptions in RuntimeExceptions einzuwickeln – und statt einer Exception dann diese RuntimeException zu werfen.
JPA z.B. wickelt alle SQLExceptions in JPA-Exceptions ein – und letztere sind RuntimeExceptions. Und auch das Springframework verwendet dieselbe Technik.
Dieses Einwickeln ist aber immer nervtötend. Betrachten wird folgendes Code-Fragment:
final Thread t = new Thread() { @Override public void run() { System.out.println("Thread starts..."); System.out.println("Simulating hard work..."); try { Thread.sleep(1000); } catch (final InterruptedException e) { throw new RuntimeException(e); } System.out.println("Thread terminates..."); } }; t.start(); System.out.println("waiting..."); try { t.join(); } catch (final InterruptedException e) { throw new RuntimeException(e); } System.out.println("go on...")
(Man beachte, dass die run-Methode der Thread-Klasse keine checked-Exception werfen darf – wir müssen also einwickeln.)
Das Einwickeln erfordert immer sechs Zeilen.
Für die folgende Diskussion verwenden wir zwei Demo-Methoden, die jeweils eine checked Exception werfen können – eine Methode foo, die nichts zurückliefert, und eine bar-Methode, die ein int–Resultat liefert:
static void foo(final int x) throws Exception { if (x <= 0) throw new Exception("argument must be positive"); System.out.println(x); }
static int bar(final int x) throws Exception { if (x >= 0) throw new Exception("argument must be negative"); return -x; }
Wir benutzen diese Methoden in einer Runnable– und einer Supplier-Implementierung. Bekanntlich darf weder die run-Methode von Runnable noch die get-Methode von Supplier eine checked-Exception werfen – wir wickeln diese Exception also in eine RuntimeException ein:
final Runnable runnable = () -> { try { foo(42); } catch (final Exception e) { throw new RuntimeException(e); } }; runnable.run();
final Supplier<Integer> supplier = () -> { try { return bar(-42); } catch (final Exception e) { throw new RuntimeException(e); } }; System.out.println(supplier.get());
Nervtötend…
Wir entwickeln eine kleine Utilitiy-Klasse:
package jn.util; public class TryCatch { public interface XRunnable { void run() throws Throwable; } public interface XSupplier<T> { T get() throws Throwable; } public static void wrapException(final XRunnable runnable) { try { runnable.run(); } catch (final Throwable t) { throw new RuntimeException(t); } } public static <T> T wrapException(final XSupplier<T> supplier) { try { return supplier.get(); } catch (final Throwable t) { throw new RuntimeException(t); } } }
Die Klasse definiert zunächst “Exception”-verträgliche Versionen der beiden Standard-Interfaces Runnable und Supplier: die Interfaces XRunnable und XSupplier.
Sie definiert dann zwei statische wrapException-Methoden. Der ersten Methode wird ein XRunnable übergeben, der zweiten ein XSupplier. Die erste Methode ruft die run-Methode des XRunnables auf und wickelt eine Exception, die von dieser Methode geworfen werden kann, in eine RuntimeException ein. Die zweite Methode ruft die get-Methode des XSuppliers auf und liefert deren Resultat als eigenes Resultat zurück (und wickelt ein…)
Eine Anwendung dieser Methode kann zunächst einen statischen Import definieren:
import static jn.util.TryCatch.wrapException;
Die Thread-Anwendung, die oben vorgestellt wurde, kann nun wesentlich konziser formuliert werden:
final Thread t = new Thread() { @Override public void run() { System.out.println("Thread starts..."); System.out.println("Simulating hard work..."); wrapException(() -> Thread.sleep(1000)); System.out.println("Thread terminates..."); } }; t.start(); System.out.println("waiting..."); wrapException(() -> t.join()); System.out.println("go on...");
Dasselbe gilt auch für das Runnable– und Supplier-Beispiel:
final Runnable runnable = () -> wrapException(() -> foo(42)); runnable.run(); final Supplier<Integer> supplier = () -> wrapException(() -> bar(-42)); System.out.println(supplier.get());
Bevor wir unsere Überlegungen weiterführen, ist zunächst eine kleine Refaktorierung angesagt. Wir können die erste wrapException-Methode auf die zweite wrapException-Methode abbilden:
public class TryCatch { // ... public static void wrapException(final XRunnable runnable) { wrapException( (XSupplier<Void>) () -> { runnable.run(); return null; }); } public static <T> T wrapException(final XSupplier<T> supplier) { try { return supplier.get(); } catch (final Throwable t) { throw new RuntimeException(t); } } }
Exceptions sollten auch in spezifische RuntimeExceptions eingewickelt werden können. Wir erweitern unsere TryCatch-Klasse um zwei weitere wrapException-Methoden, denen zusätzlich zu dem XRunnable resp. dem XSupplier jeweils eine Function übergeben wird – ein exceptionWrapper, welchem ein Throwable übergeben wird und welcher dann eine RuntimeException liefern muss:
import java.util.function.Function; public class TryCatch { // ... public static void wrapException( final XRunnable runnable, final Function<Throwable, RuntimeException> exceptionWrapper) { wrapException((XSupplier<Void>) () -> { runnable.run(); return null; }, exceptionWrapper); } public static <T> T wrapException( final XSupplier<T> supplier, final Function<Throwable, RuntimeException> exceptionWrapper) { try { return supplier.get(); } catch (final Throwable t) { throw exceptionWrapper.apply(t); } } }
Angenommen nun, die Anwendung definiert folgende RuntimeException-Klasse:
class MyException extends RuntimeException { private static final long serialVersionUID = 1L; public MyException(final String msg, final Throwable t) { super(msg, t); } @Override public String toString() { return this.getClass().getName() + " [" + this.getMessage() + ", " + this.getCause() + "]"; } }
Dann können die beiden neuen wrapException-Klassen wie folgt genutzt werden:
final Runnable runnable = () -> wrapException( () -> foo(42), e -> new MyException("Hello", e)); runnable.run(); final Supplier<Integer> supplier = () -> wrapException( () -> bar(42), e -> new MyException("World", e)); System.out.println(supplier.get());
Auch hier ist ein wenig Refaktorierung angesagt. Wir können eine der beiden alten wrapException-Methode auf die neue abbilden:
public static <T> T wrapException(final XSupplier<T> supplier) { return wrapException(supplier, e -> new RuntimeException(e)); } public static <T> T wrapException( final XSupplier<T> supplier, final Function<Throwable, RuntimeException> exceptionWrapper) { try { return supplier.get(); } catch (final Throwable t) { throw exceptionWrapper.apply(t); } }
Hier noch einmal der komplette Quellcode von TryCatch:
package jn.util; import java.util.function.Function; public class TryCatch { public interface XRunnable { void run() throws Throwable; } public interface XSupplier<T> { T get() throws Throwable; } public static void wrapException(final XRunnable runnable) { wrapException( (XSupplier<Void>) () -> { runnable.run(); return null; }); } public static void wrapException( final XRunnable runnable, final Function<Throwable, RuntimeException> exceptionWrapper) { wrapException( (XSupplier<Void>) () -> { runnable.run(); return null; }, exceptionWrapper); } public static <T> T wrapException(final XSupplier<T> supplier) { return wrapException(supplier, e -> new RuntimeException(e)); } public static <T> T wrapException( final XSupplier<T> supplier, final Function<Throwable, RuntimeException> exceptionWrapper) { try { return supplier.get(); } catch (final Throwable t) { throw exceptionWrapper.apply(t); } } }
Seminare zum Thema:
Clean Code – Professionelle Codeerstellung und Wartung