以功能方式处理异常的更好方法

Mar*_*nik 58 java java-8 java-stream

在Java 8中使用FP习惯用法时,异常(尤其是已检查的异常)会严重中断程序逻辑的流程.以下是一个任意示例:

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

当不可解析的字符串出现异常时,上面的代码会中断.但是说我只想用默认值替换它,就像我可以用Optional:

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

当然,这仍然失败因为Optional只处理nulls.我想要的东西如下:

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

注意:这是一个自我回答的问题.

Mar*_*nik 53

下面提供的是该Exceptional课程的完整代码.它有一个非常大的API,它是API的纯扩展,Optional因此它可以在任何现有代码中替代它 - 除了它不是最终Optional类的子类型.这个类可以被看作是与相同的关系是Try因为单子Optional是与Maybe单子:它的设计灵感来自它,但是适应了Java的成语(如实际抛出异常,甚至从非终端操作).

这些是课程遵循的一些关键指导原则:

  • 与monadic方法相反,不忽略Java的异常机制;

  • 相反,它减轻了异常和高阶函数之间的阻抗不匹配;

  • 异常处理不是静态类型安全(由于偷偷摸摸),但在运行时始终是安全的(除非在显式请求中,否则永远不会吞下异常).

该类试图涵盖处理异常的所有典型方法:

  • recover 一些处理代码,提供替代值;
  • flatRecover类似于flatMap,它允许返回一个Exceptional将被解包的新实例,并且适当更新当前实例的状态;
  • propagate一个例外,从Exceptional表达式抛出它并使propagate调用声明这个异常类型;
  • propagate它包装成另一个异常后(翻译它);
  • handle它,导致空虚Exceptional;
  • 作为一个特殊的处理案例,swallow它带有一个空的处理程序块.

propagate方法允许人们有选择地从他的代码中选择他想要公开的检查异常.在调用终端操作时仍然未处理的异常(例如get)将在没有声明的情况下偷偷抛出.这通常被认为是一种先进的和危险的做法,但仍然被经常使用,以此来减轻几分联合检查的异常与拉姆达形状不声明它们的滋扰.该Exceptional课程希望为偷偷摸摸的投掷提供更清洁,更具选择性的选择.


/*
 * 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;
  }
}
Run Code Online (Sandbox Code Playgroud)
@FunctionalInterface
public interface TrySupplier<T> {
  T tryGet() throws Throwable;
}
Run Code Online (Sandbox Code Playgroud)
@FunctionalInterface
public interface TryRunnable {
  void run() throws Throwable;
}
Run Code Online (Sandbox Code Playgroud)

  • @ the8472 Stackoverflow => [知识共享](http://creativecommons.org/licenses/by-sa/3.0/) (4认同)
  • 如果您决定将此类放在某个存储库中,请不要忘记更新您的答案:) (3认同)
  • @MarkoTopolnik当编译器试图推断出"X extends Throwable"的类型时,它会[更喜欢未经检查的异常](http://docs.oracle.com/javase/specs/jls/se8/html/jls-18. html#jls-18.1.3):`它指示解析来优化α的实例化,以便在可能的情况下,它不是一个经过检查的异常类型. (3认同)
  • 有人建议在C++中添加类似的类型,名为`expected`.正如`expected <exception,int>`应该是`int`,但如果不是,那么原因是`exception`.请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4015.pdf - 它类似,但与异常相关性较小. (3认同)
  • 可以在此处找到 Mario Fusco 为 Java 编写的“Try”monad 的另一个示例:https://github.com/mariofusco/javaz/blob/master/src/main/java/org/javaz/Try.java。 (2认同)
  • @assylias我很想过它,但考虑到`asList(Exception1.class,Exception2.class)`的可用性,我认为不值得失去直观性. (2认同)
  • @yakk它实际上更像是'Try` monad(左偏),但是我想指出我所看到的是我的类和monad之间的主要区别:`expected`*封装*失败作为值而不是我的班级只允许一个更好的习语来强制处理异常.就像`Optional`是一个用表达式替换`if`语句的工具(并避免一些样板空值检查),因此`Exceptional`是一个用`try` -`catch`语句做同样的工具. (2认同)

Jef*_*rey 10

如果提供的每个功能接口都java.util.function被允许抛出异常怎么办?

public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用一些默认方法来提供您想要的行为.

  • 您可以回退到某个默认值或操作
  • 或者您可以尝试执行可能引发异常的其他操作

编写了一个java.util.function这种方式重新定义大多数接口的库.我甚至提供了一个ThrowingStream让你使用这些新接口与常规API相同的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;
                }
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

(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);
Run Code Online (Sandbox Code Playgroud)


Zho*_*gYu 6

以下是我之前就此主题进行的一些讨论.

Result<T>按照推理做了一个界面.A Result<T>是具有值类型的成功T,或具有异常的失败.它是一个子类型Async<T>,作为立即完成的异步操作,但这在这里并不重要.

要创建结果 -

Result.success( value )
Result.failure( exception )
Result.call( callable )
Run Code Online (Sandbox Code Playgroud)

然后可以以各种方式转换结果 - transform, map, then, peek, catch_, finally_例如

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_( ()->{...} )
Run Code Online (Sandbox Code Playgroud)

不幸的是,API专注于Async,因此一些方法返回Async.其中一些可以被Result覆盖以返回Result; 但有些不能,例如then()(这是flatmap).但是,如果感兴趣,很容易提取与Async无关的独立Result API.

  • @Jeffrey - 更具体地说,我不喜欢它 - 取消机制; 可变性(!); 在lambda体中没有检查过的异常; 膨胀的API,长类/方法名称; 太多的方法而不是更少的可组合方法; 缺乏一些方便的方法. (2认同)

Tag*_*eev 5

有一个名为better-java-monads的第三方库.它具有Try提供必要功能的monad.它也有TryMapFunctionTrySupplier功能接口使用Try与checked异常单子.

  • 我实际上是设计了我的课程,以回应我在"Either"和"Try"中看到的内容.他们只是反复出现了同样众所周知的monad主题,我认为这些主题在Haskell之外并不特别实用.他们严格遵守纯粹的FP,无国籍和不变性的宗教.几乎所有东西都表示为类型转换,它适合像Haskell这样强大的类型系统,比Java好得多.此外,它们与标准库和现有代码的其余部分不能很好地融合; 例如,通过完全回避异常系统. (3认同)