Обработка ошибок в функциональном стиле в Java

в 6:03, , рубрики: java, kotlin, pattern matching, vavr

Кроме классического подхода для обработки ошибок с помощью исключений, можно выделить также функциональный подход.

Вместо того, чтобы кидать исключение сразу, можно его локализировать, а потом выполнить над ним определеные действия.

Например, в языке Scala для этого используется определенный класс Try.

def inputStreamForURL(url: String): Try[Try[Try[InputStream]]] = parseURL(url).map { u =>
     Try(u.openConnection()).map(conn => Try(conn.getInputStream))
}

В Java мире с помощью библиотеки Vavr также можно обрабатывать ошибки в функциональном стиле.

Try.of(() -> u.openConnection()).getOrElse(other);

В Java 8 для более коретной работы с null типами был добавлен класс Optional. Он позволяет обворачить объект, который может быть null, и в функциональном стиле безопасно с ним дальше работать.

Для работы с разными типами исключений, можно реализовать подобный к Optional класс, такой же потокобезопасный и немутабельный.

public final class Expected<T, E extends Throwable> {
    private final T value;
    private final E error;

    private Expected() {
        this.value = null;
        this.error = null;
    }

    private Expected(T value) {
        this.value = Objects.requireNonNull(value);
        this.error = null;
    }

    private Expected(E error) {
        this.error = Objects.requireNonNull(error);
        this.value = null;
    }

    public static <T, E extends Throwable> Expected<T, E> of(T value) {
        return new Expected<>(value);
    }

    public static <T, E extends Throwable> Expected<T, E> of(E error) {
        return new Expected<>(error);
    }

    public static <T, E extends Throwable> Expected<T, E> of(Supplier<T> supplier) {
        try {
            return new Expected<>(supplier.get());
        } catch (Throwable e) {
            return new Expected<>((E) e);
        }
    }

    public boolean isValue() {
        return value != null;
    }

    public boolean isError() {
        return error != null;
    }

    public T value() {
        return value;
    }

    public E error() {
        return error;
    }
}

Также для проверки было брошено исключение или нет можно написать простой визитер.

Expected<Integer, SQLException> data = Expected.of(new SQLException());

matches(data,
        Expected::error,  e -> out.println("get error: " + e),
	Expected::value, v -> out.println("get value: " + v)
);

Expected<Integer, ArithmeticException> expression = Expected.of(() -> 4 / 0);

matches(expression,
	Expected::error,  e -> out.println("get error: " + e),
	Expected::value, v -> out.println("get value: " + v)
);

public static <T, E extends Throwable>
void matches(Expected<T, E> value,
         Function<Expected<T, E>, E> firstFunction,  Consumer<E> firstBranch,
         Function<Expected<T, E>, T> secondFunction, Consumer<T> secondBranch) {
       if (value.isError()) {
            E arg = firstFunction.apply(value);
            firstBranch.accept(arg);
       } else {
            T arg = secondFunction.apply(value);
            secondBranch.accept(arg);
       }
}

В С++23 планируется добавить подобный класс в стандартную библиотеку.

Такой класс мог бы составить хорошую компанию Optional в стандартной библиотеке Java. Но увы, в данный момент, чтобы работать с исключения в функциональном стиле можно использовать библиотеку Vavr или писать свои классы на подобии к Expected.

Полный исходной код класса можно посмотреть на github: link.

Автор: koowaah

Источник


* - обязательные к заполнению поля