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

Posted

技术标签:

【中文标题】以功能方式处理异常的更好方法【英文标题】:A better approach to handling exceptions in a functional way 【发布时间】:2015-09-25 01:41:19 【问题描述】:

当在 Java 8 中使用 FP 习语时,异常,尤其是已检查的异常,会严重中断程序逻辑的流程。下面是一个任意示例:

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)));

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

【问题讨论】:

对于orElse 部分,而不是返回-1,有没有办法只是省略元素。因此,如果存在异常元素,则生成的流将具有更少的元素。 在提供的示例中,Exceptional 在终端操作中进入图片,其中流的内容已经确定。你可以写一个涉及flatMap阶段的不同表达式,你可以在Exceptional中添加一个.stream()方法:Stream<T> stream() return isPresent() ? Stream.of(value) : Stream.empty(); 然后你可以说stream.flatMap(Exceptional::stream) 【参考方案1】:

下面是Exceptional 类的完整代码。它有一个相当大的 API,它是 Optional API 的纯扩展,因此它可以在任何现有代码中直接替代它——除了它不是最终 Optional 类的子类型。该类可以被视为与Try monad 的关系与OptionalMaybe monad 的关系相同:它从中汲取灵感,但适应了Java 习惯用法(例如实际抛出异常,甚至来自非终端操作)。

这些是课程遵循的一些关键准则:

与一元方法相反,它不会忽略 Java 的异常机制;

反而缓解了异常和高阶函数之间的阻抗不匹配;

异常处理不是静态类型安全的(由于偷偷抛出),但在运行时始终是安全的(除非明确请求,否则永远不会吞下异常)。

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

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

propagate 方法允许人们有选择地选择他想从他的代码中公开哪些检查异常。在调用终端操作时仍未处理的异常(如get)将在没有声明的情况下偷偷地抛出。这通常被认为是一种先进且危险的方法,但仍然经常被用作一种在某种程度上减轻检查异常与未声明它们的 lambda 形状相结合的滋扰的方法。 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;
  


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


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

【讨论】:

如果您决定将此类放在某个存储库中,请不要忘记更新您的答案 :) @the8472 *** => Creative Commons Mario Fusco 为 java 编写的 'Try' monad 的另一个例子可以在这里找到:github.com/mariofusco/javaz/blob/master/src/main/java/org/javaz/…。 @MarkoTopolnik 当编译器试图推断X extends Throwable的类型时,它将prefer an unchecked exception:it directs resolution to optimize the instantiation of α so that, if possible, it is not a checked exception type 有人提议在 C++ 中添加类似的类型,名称为 expected。如expected&lt;exception, int&gt; 预计为int,但如果不是,原因是exception。参见open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4015.pdf——它很相似,但与异常的联系较少。【参考方案2】:

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

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

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

您可以回退到一些默认值或操作 或者您可以尝试执行另一个可能引发异常的操作

我有written a library,它以这种方式重新定义了java.util.function 中的大部分接口。我什至提供了一个ThrowingStream,让您可以使用这些新接口,使用与常规Stream 相同的API。

@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);

【讨论】:

【参考方案3】:

这里有一些 discussions 我之前关于这个话题的内容。

我顺着推理做了一个界面Result&lt;T&gt;Result&lt;T&gt; 要么是成功且具有 T 类型的值,要么是失败并带有异常。它是Async&lt;T&gt; 的子类型,作为立即完成的异步操作,但这在这里并不重要。

创建结果 -

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

然后可以通过各种方式转换结果 - 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_( ()->... )

不幸的是,API 专注于异步,因此一些方法返回异步。其中一些可以被 Result 覆盖以返回 Result;但有些不能,例如then()(这是平面图)。但是,如果有兴趣,可以轻松提取与 Async 无关的独立 Result API。

【讨论】:

这看起来非常符合我的目标。但是,绝对行不通的一件事是finally_(),因为之前的任何步骤都可能已经引发了异常。为了完成这项工作,整个实现必须是惰性的,只需记住您要求的步骤,然后在终端操作上重放它们。我也很确定偷偷摸摸的投掷永远不会进入像这样的 API :) 啊,好的。那么它可能和我的想法不一样,实际上是抛出异常(更准确地说,在 API 中有两种选择)。 我的一个关键想法是选择性声明受检异常——所以我需要的不仅仅是getOrThrow。我需要在应用类型过滤器的情况下进行投掷。 我的主要用例是在方法内部或 lambda 主体内部使用Exceptional。你调用一个方法,得到一堆检查的异常。您希望将IOException 传播为已检查的,但您只想将一堆反射异常视为失败。 @Jeffrey - 更具体地说,我不喜欢它的地方 - 取消机制;可变性(!); lambda 正文中没有已检查的异常;臃肿的 API,长的类/方法名;太多的方法而不是更少的可组合方法;缺少一些方便的方法。【参考方案4】:

有一个名为better-java-monads 的第三方库。它具有提供必要功能的Try monad。它还具有TryMapFunctionTrySupplier 功能接口,以使用带有检查异常的Try monad。

【讨论】:

实际上我设计我的课程是为了回应我在EitherTry 中看到的内容。它们只是重复了同样著名的 monad 主题,我觉得在 Haskell 之外并不特别实用。他们严格遵守纯FP、无国籍和不变性的宗教。几乎所有东西都表示为类型转换,它适合像 Haskell 这样强大的类型系统,比 Java 好得多。此外,它们与标准库的其余部分和现有代码不能很好地结合;例如完全避开异常系统。

以上是关于以功能方式处理异常的更好方法的主要内容,如果未能解决你的问题,请参考以下文章

如何以原子方式运行 2 个 SQL“SELECT”?或者在我处理它们之前获取行数的任何其他更好的方法

什么情况下可以使用异常处理? [关闭]

以最佳方式处理 UISlider 值

异常-throws的方式处理异常

如何捕获 SQLServer 超时异常

Netty中如果触发了异常其怎么处理逻辑