Pull to refresh

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

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

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

Например, в языке 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.
Tags:
Hubs:
Total votes 11: ↑8 and ↓3+5
Comments13

Articles