Лучший подход к обработке исключений функциональным способом



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



String s1 = "oeu", s2 = "2";
Stream.of(s1, s2).forEach(s ->
System.out.println(Optional.of(s).map(Integer::parseInt).get()));


приведенный выше код прерывается, когда есть исключение для непростительной строки. Но скажите, что я просто хочу заменить это значение по умолчанию, так же, как я могу с Optional:



Stream.of(s1, s2).forEach(s -> 
System.out.println(Optional.of(s)
.map(Integer::parseInt)
.orElse(-1)));


конечно, это все равно не удается, потому что Optional обрабатывает только nulls. Я хотел бы что-то как следует:



Stream.of(s1, s2).forEach(s ->
System.out.println(
Exceptional.of(s)
.map(Integer::parseInt)
.handle(NumberFormatException.class, swallow())
.orElse(-1)));




Примечание: это ответ на вопрос.

562   4  

4 ответов:

ниже представлен полный код Exceptional класса. Он имеет довольно большой API, который является чистым продолжением Optional API, поэтому он может быть заменой для него в любом существующем коде-за исключением того, что он не является подтипом final Optional класса. Класс можно рассматривать как находящийся в тех же отношениях с Try монады как Optional С Maybe монада: он черпает вдохновение из него, но адаптирован к идиоме Java (например, фактически бросая исключения, даже из нетерминальных операций).

вот некоторые ключевые рекомендации, которым следует класс:

  • в отличие от монадического подхода, не игнорирует механизм исключения Java;

  • вместо этого он снимает несоответствие импеданса между исключениями и функциями более высокого порядка;

  • обработка исключений не статически типобезопасна (из-за скрытого броска), но всегда безопасна во время выполнения (никогда не проглатывает исключение, кроме как по явному запросу).

класс пытается охватить все типичные способы обработки исключения:

  • recover С некоторым кодом обработки, который обеспечивает заменяющее значение;
  • flatRecover, который, по аналогии с flatMap, позволяет вернуть новый Exceptional экземпляр, который будет развернут и состояние текущего экземпляра своевременного обновления;
  • propagate исключение, бросая его из Exceptional выражение и делает propagate призыв объявить этот тип исключения;
  • propagate это после обертывания в другое исключение (перевести it);
  • handle это, в результате чего пустой Exceptional;
  • как частный случай обработки, swallow это с пустым блоком обработчика.

The propagate подход позволяет выборочно выбирать, какие проверенные исключения он хочет выставить из своего кода. Исключения, которые остаются необработанными во время вызова терминальной операции (например get) будет удалить бросил без объявления. Это часто рассматривается как продвинутый и опасный подход, но тем не менее часто используется как способ несколько облегчить неудобство проверенных исключений в сочетании с лямбда-формами, которые их не объявляют. Элемент Exceptional класс надеется предложить более чистую и более избирательную альтернативу sneaky бросать.


/*
 * Copyright (c) 2015, Marko Topolnik. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public final class Exceptional<T>
{
  private final T value;
  private final Throwable exception;

  private Exceptional(T value, Throwable exc) {
    this.value = value;
    this.exception = exc;
  }

  public static <T> Exceptional<T> empty() {
    return new Exceptional<>(null, null);
  }

  public static <T> Exceptional<T> ofNullable(T value) {
    return value != null ? of(value) : empty();
  }

  public static <T> Exceptional<T> of(T value) {
    return new Exceptional<>(Objects.requireNonNull(value), null);
  }

  public static <T> Exceptional<T> ofNullableException(Throwable exception) {
    return exception != null? new Exceptional<>(null, exception) : empty();
  }

  public static <T> Exceptional<T> ofException(Throwable exception) {
    return new Exceptional<>(null, Objects.requireNonNull(exception));
  }

  public static <T> Exceptional<T> from(TrySupplier<T> supplier) {
    try {
      return ofNullable(supplier.tryGet());
    } catch (Throwable t) {
      return new Exceptional<>(null, t);
    }
  }

  public static Exceptional<Void> fromVoid(TryRunnable task) {
    try {
      task.run();
      return new Exceptional<>(null, null);
    } catch (Throwable t) {
      return new Exceptional<>(null, t);
    }
  }

  public static <E extends Throwable> Consumer<? super E> swallow() {
    return e -> {};
  }

  public T get() {
    if (value != null) return value;
    if (exception != null) sneakyThrow(exception);
    throw new NoSuchElementException("No value present");
  }

  public T orElse(T other) {
    if (value != null) return value;
    if (exception != null) sneakyThrow(exception);
    return other;
  }

  public T orElseGet(Supplier<? extends T> other) {
    if (value != null) return value;
    if (exception != null) sneakyThrow(exception);
    return other.get();
  }

  public Stream<T> stream() { 
      return value == null ? Stream.empty() : Stream.of(value); 
  }

  public<U> Exceptional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (value == null) return new Exceptional<>(null, exception);
    final U u;
    try {
      u = mapper.apply(value);
    } catch (Throwable exc) {
      return new Exceptional<>(null, exc);
    }
    return ofNullable(u);
  }

  public<U> Exceptional<U> flatMap(Function<? super T, Exceptional<U>> mapper) {
    Objects.requireNonNull(mapper);
    return value != null ? Objects.requireNonNull(mapper.apply(value)) : empty();
  }

  public Exceptional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (value == null) return this;
    final boolean b;
    try {
      b = predicate.test(value);
    } catch (Throwable t) {
      return ofException(t);
    }
    return b ? this : empty();
  }

  public <X extends Throwable> Exceptional<T> recover(
      Class<? extends X> excType, Function<? super X, T> mapper)
  {
    Objects.requireNonNull(mapper);
    return excType.isInstance(exception) ? ofNullable(mapper.apply(excType.cast(exception))) : this;
  }

  public <X extends Throwable> Exceptional<T> recover(
      Iterable<Class<? extends X>> excTypes, Function<? super X, T> mapper)
  {
    Objects.requireNonNull(mapper);
    for (Class<? extends X> excType : excTypes)
      if (excType.isInstance(exception))
        return ofNullable(mapper.apply(excType.cast(exception)));
    return this;
  }

  public <X extends Throwable> Exceptional<T> flatRecover(
      Class<? extends X> excType, Function<? super X, Exceptional<T>> mapper)
  {
    Objects.requireNonNull(mapper);
    return excType.isInstance(exception) ? Objects.requireNonNull(mapper.apply(excType.cast(exception))) : this;
  }

  public <X extends Throwable> Exceptional<T> flatRecover(
      Iterable<Class<? extends X>> excTypes, Function<? super X, Exceptional<T>> mapper)
  {
    Objects.requireNonNull(mapper);
    for (Class<? extends X> c : excTypes)
      if (c.isInstance(exception))
        return Objects.requireNonNull(mapper.apply(c.cast(exception)));
    return this;
  }

  public <E extends Throwable> Exceptional<T> propagate(Class<E> excType) throws E {
    if (excType.isInstance(exception))
      throw excType.cast(exception);
    return this;
  }

  public <E extends Throwable> Exceptional<T> propagate(Iterable<Class<? extends E>> excTypes) throws E {
    for (Class<? extends E> excType : excTypes)
      if (excType.isInstance(exception))
        throw excType.cast(exception);
    return this;
  }

  public <E extends Throwable, F extends Throwable> Exceptional<T> propagate(
      Class<E> excType, Function<? super E, ? extends F> translator)
  throws F
  {
    if (excType.isInstance(exception))
      throw translator.apply(excType.cast(exception));
    return this;
  }

  public <E extends Throwable, F extends Throwable> Exceptional<T> propagate(
      Iterable<Class<E>> excTypes, Function<? super E, ? extends F> translator)
  throws F
  {
    for (Class<? extends E> excType : excTypes)
      if (excType.isInstance(exception))
        throw translator.apply(excType.cast(exception));
    return this;
  }

  public <E extends Throwable> Exceptional<T> handle(Class<E> excType, Consumer<? super E> action) {
    if (excType.isInstance(exception)) {
      action.accept(excType.cast(exception));
      return empty();
    }
    return this;
  }

  public <E extends Throwable> Exceptional<T> handle(Iterable<Class<E>> excTypes, Consumer<? super E> action) {
    for (Class<? extends E> excType : excTypes)
      if (excType.isInstance(exception)) {
        action.accept(excType.cast(exception));
        return empty();
      }
    return this;
  }

  public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) return value;
    if (exception != null) sneakyThrow(exception);
    throw exceptionSupplier.get();
  }

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

  public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
      consumer.accept(value);
    if (exception != null) sneakyThrow(exception);
  }

  public boolean isException() {
    return exception != null;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    return obj instanceof Exceptional && Objects.equals(value, ((Exceptional)obj).value);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(value);
  }

  @SuppressWarnings("unchecked")
  private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
    throw (T) t;
  }
}

@FunctionalInterface
public interface TrySupplier<T> {
  T tryGet() throws Throwable;
}

@FunctionalInterface
public interface TryRunnable {
  void run() throws Throwable;
}

Что делать, если каждый функциональный интерфейс, предоставленный java.util.function было разрешено бросить исключение?

public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}

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

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

Я написано библиотека что переопределяет большинство интерфейсов в java.util.function этот путь. Я даже предоставляю ThrowingStream что позволяет вам использовать эти новые интерфейсы с тем же API, что и обычный Stream.

@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;

    default public Supplier<R> fallbackTo(Supplier<? extends R> supplier) {
        ThrowingSupplier<R, Nothing> t = supplier::get;
        return orTry(t)::get;
    }

    default public <Y extends Throwable> ThrowingSupplier<R, Y> orTry(
            ThrowingSupplier<? extends R, ? extends Y> supplier) {
        Objects.requireNonNull(supplier, "supplier");
        return () -> {
            try {
                return get();
            } catch (Throwable x) {
                try {
                    return supplier.get();
                } catch (Throwable y) {
                    y.addSuppressed(x);
                    throw y;
                }
            }
        };
    }
}

(Nothing это RuntimeException это никогда не может быть брошено.)


ваш оригинальный пример станет

ThrowingFunction<String, Integer, NumberFormatException> parse = Integer::parseInt;
Function<String, Optional<Integer>> safeParse = parse.fallbackTo(s -> null)
    .andThen(Optional::ofNullable);
Stream.of(s1, s2)
    .map(safeParse)
    .map(i -> i.orElse(-1))
    .forEach(System.out::println);

вот обсуждения я уже ранее на эту тему.

Я сделал интерфейс Result<T> по рассуждениям. А Result<T> либо успех со значением типа T, или отказе. Это подтип Async<T>, как немедленно завершенное асинхронное действие, но это не важно здесь.

создать результат -

Result.success( value )
Result.failure( exception )
Result.call( callable )

результат может быть преобразован различными способами - transform, map, then, peek, catch_, finally_ etc. Например

Async<Integer> rInt = Result.success( s )
      .map( Integer::parseInt )
      .peek( System.out::println )
      .catch_( NumberFormatException.class, ex->42 ) // default
      .catch_( Exception.class, ex-> { ex.printStacktrace(); throw ex; } )
      .finally_( ()->{...} )

к сожалению, API фокусируется на асинхронности, поэтому некоторые методы возвращают асинхронность. Некоторые из них могут быть переопределены результатом для возврата результата; но некоторые не могут, например then() (что является flatmap). Однако, если это интересно, легко извлечь автономный API результатов, который не имеет ничего общего с асинхронностью.

есть сторонняя библиотека лучше-java-монады. Он имеет Try монада, которая обеспечивает необходимые функции. Он также имеет TryMapFunction и TrySupplier функциональные интерфейсы для использования Try монада с проверенными исключениями.

Comments

    Ничего не найдено.