Einwickeln von Exceptions

Über das Einwickeln von Exceptions

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

Java 8 / JDK8 Update

Java Erweiterungen II – Vertiefung

Johannes Nowak / Johannes Nowak

Nach seinem Studium der Soziologie und Philosophie 13 Jahre in der Software-Entwicklung und -Architektur tätig (C/C++ - in erster Linie technische Anwendungen). Seit mehr als 20 Jahren nun als Trainer, Berater und Coach vorwiegend im Bereich objektorientierter Sprachen und Java-EE tätig. Bei Integrata trainiert er u.a. Seminare zu C++, C#, Java, Scala, Ceylon.

Schreibe einen Kommentar