ネコと和解せよ

技術的なあれこれの備忘録のつもり

Javaでラムダ式を使ってtry-catchの共通化を試みる

tl;dr

  • 次のような処理のtry-catch節の共通化を試みる
class Main {
    public static void main(String[] args) {
        try {
            // 処理1
        } catch (MyException e) {
            // 共通処理
        }

        try {
            // 処理2
        } catch (MyException e) {
            // 共通処理
        }
    }
}
  • Exceptionをthrowできる Function Interfaceを定義する
  • 実効する処理のFunctionとエラーハンドラ-を受け取る関数でtry-catchする

今回のソースコードはすべて以下にある
github.com

今回実装した方法より良い方法があれば教えてほしい。 あまりJavaの経験がないのでほぼ我流になってしまっている...。

通化しない例

処理1の成功の可否に関わらず処理2を実行したときがあると思う。 おそらく以下のようなコードを書くことになる

class Main {
    public static void main(String[] args) {
        try {
            // 処理1
            doSomething1();
        } catch (MyException e) {
            // 共通処理
        }

        try {
            // 処理2
            doSomething2();
        } catch (MyException e) {
            // 共通処理
        }
    }

    public static void doSomething1() throws MyException {
        // 処理1
        throw new MyException();
    }

    public static void doSomething2() throws MyException {
        // 処理2
        throw new MyException();
    }
}

処理2回ぐらいなら諦めも付くが、これが10回ぐらい続くと書くのも読むのも辛いので共通化したい。

はじめに最終形を記載しておく

public class Main {
    public static void main(String[] args) {
        // エラーを処理する関数
        Consumer<MyException> errorHandler = (e) -> {
            // 共通処理
        };

        tryCatch(Main::doSomething1, errorHandler);
        tryCatch(Main::doSomething2, errorHandler);
    }

    public static void tryCatch(RunnableThrowableMyException func, Consumer<MyException> errorHandler) {
        try {
            func.run();
        } catch (MyException e) {
            errorHandler.accept(e);
        }
    }

    public static void doSomething1() throws MyException {
        // 処理1
        throw new MyException();
    }

    public static void doSomething2() throws MyException {
        // 処理2
        throw new MyException();
    }
}

MyExceptionの定義

今回使う自作のExceptionクラスを定義しておく。 単純にExceptionクラスを継承する自作のExceptionクラスで、とくに変わったことはしてないと思う。

package exceptions;

public class MyException extends Exception {
    public MyException() {
    }

    public MyException(Exception e) {
        super(e);
    }
}

今回の処理1, 2では問題が起きたときこのMyExceptionをthrowする

Exceptionをthrowできる関数Interfaceを定義する

もう一度共通化前にソースを見る。

class Main {
    public static void main(String[] args) {
        try {
            // 処理1
        } catch (MyException e) {
            // 共通処理
        }

        try {
            // 処理2
        } catch (MyException e) {
            // 共通処理
        }
    }
}

まず考えるのは処理1, 2とエラーハンドリングの共通処理をラムダ式にして、 一つの関数で処理するような以下のようなコードだと思う。

public class Main {
    public static <R> void tryCatch(Runnable func, Function<MyException, R> errorHandler) {
        try {
            func.run();
        } catch (MyException e) {
            errorHandler.apply(e);
        }
    }

    public static void main(String[] args) {
        // エラーを処理する関数
        Function<MyException, Void> errorHandler = (e) -> {
            // 共通処理
            return null;
        };

        tryCatch(Main::doSomething1, errorHandler);
        tryCatch(Main::doSomething2, errorHandler);
    }
}

試しにこのコードを書いてみると RunnableはMyExceptionをthrowしないので駄目っぽい。

そこでMyExceptionをthrowできるRunnable Interfaceを定義する

以下のRunnable.javaを参考に github.com

MyExceptionをthrowするRunnable Interfaceを定義する

@FunctionalInterface
public interface RunnableThrowableMyException {
    void run() throws MyException;
}

処理とエラーハンドラーを受け取って処理する

RunnableThrowableMyExceptionを実装したので、 処理とエラーハンドラーを受け取ってtry-catchするメソッドを実装する

public class Main {
    public static void tryCatch(RunnableThrowableMyException func, Consumer<MyException> errorHandler) {
        try {
            func.run(); // 処理を実行。MyExceptionがthrowされる
        } catch (MyException e) {
            errorHandler.accept(e); // 受け取ったエラーハンドラーで処理する
        }
    }

    public static void main(String[] args) {
        // エラーを処理する関数
        Consumer<MyException> errorHandler = (e) -> {
            // 共通処理
        };

        tryCatch(Main::doSomething1, errorHandler);
        tryCatch(Main::doSomething2, errorHandler);
    }

    public static void doSomething1() throws MyException {
        // 処理1
        throw new MyException();
    }

    public static void doSomething2() throws MyException {
        // 処理2
        throw new MyException();
    }
}

もう少し推し進めて、処理をListで受け取るようにする

public class Main {
    public static void tryCatch(RunnableThrowableMyException func, Consumer<MyException> errorHandler) {
        try {
            func.run(); // 処理を実行。MyExceptionがthrowされる
        } catch (MyException e) {
            errorHandler.accept(e); // 受け取ったエラーハンドラーで処理する
        }
    }

    public static void tryCatch(List<RunnableThrowableMyException> functions, Consumer<MyException> errorHandler) {
        // 処理をリストで受け取って処理する
        functions.forEach(func -> tryCatch(func, errorHandler));
    }

    public static void main(String[] args) {
        // エラーを処理する関数
        Consumer<MyException> errorHandler = (e) -> {
            // 共通処理
        };

        List<RunnableThrowableMyException> functions = new ArrayList<RunnableThrowableMyException>(){
            {
                add(Main::doSomething1);
                add(Main::doSomething2);
            }
        };
        TryCatch.tryCatch(functions, errorHandler);
    }

    public static void doSomething1() throws MyException {
        // 処理1
        throw new MyException();
    }

    public static void doSomething2() throws MyException {
        // 処理2
        throw new MyException();
    }
}

今回のソースコードはすべて以下にある
github.com