捕获一般异常

Zoe*_*wll 5 java generics functional-programming exception-handling

问题

我正在Result用Java 编写一个类型,我发现它需要一个执行可能失败的操作的方法,然后在新的Result对象中封装值或异常.

我曾希望这会奏效:

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

public class Result<E extends Throwable, V>
{
  ...
  public static <E extends Throwable, V> Result<E, V> of(ThrowingSupplier<V, E> v)
  {
    try
    {
      return value(v.get());
    }
    catch(E e)
    {
      return error(e);
    }
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

但Java无法捕获由类型参数定义的异常.我也尝试过使用instanceof,但也不能用于泛型.有什么方法可以实现这个方法吗?

定义

这是添加of方法之前的结果类型.它的目的是类似于HaskellEitherrustResult,同时也有一个有意义的bind操作:

public class Result<E extends Throwable, V>
{
  private Either<E, V> value;

  private Result(Either<E, V> value)
  {
    this.value = value;
  }

  public <T> T match(Function<? super E, ? extends T> ef, Function<? super V, ? extends T> vf)
  {
    return value.match(ef, vf);
  }

  public void match(Consumer<? super E> ef, Consumer<? super V> vf)
  {
    value.match(ef, vf);
  }

  /**
   * Mirror of haskell's Monadic (>>=)
   */
  public <T> Result<E, T> bind(Function<? super V, Result<? extends E, ? extends T>> f)
  {
    return match(
        (E e) -> cast(error(e)),
        (V v) -> cast(f.apply(v))
    );
  }

  /**
   * Mirror of Haskell's Monadic (>>) or Applicative (*>)
   */
  public <T> Result<E, T> then(Supplier<Result<? extends E, ? extends T>> f)
  {
    return bind((__) -> f.get());
  }

  /**
   * Mirror of haskell's Applicative (<*)
   */
  public Result<E, V> peek(Function<? super V, Result<? extends E, ?>> f)
  {
    return bind(v -> f.apply(v).then(() -> value(v)));
  }

  public <T> Result<E, T> map(Function<? super V, ? extends T> f)
  {
    return match(
        (E e) -> error(e),
        (V v) -> value(f.apply(v))
    );
  }

  public static <E extends Throwable, V> Result<E, V> error(E e)
  {
    return new Result<>(Either.left(e));
  }

  public static <E extends Throwable, V> Result<E, V> value(V v)
  {
    return new Result<>(Either.right(v));
  }

  /**
   * If the result is a value, return it.
   * If it is an exception, throw it.
   *
   * @return the contained value
   * @throws E the contained exception
   */
  public V get() throws E
  {
    boolean has = match(
        e -> false,
        v -> true
    );
    if (has)
    {
      return value.fromRight(null);
    }
    else
    {
      throw value.fromLeft(null);
    }
  }

  /**
   * Upcast the Result's type parameters
   */
  private static <E extends Throwable, V> Result<E, V> cast(Result<? extends E, ? extends V> r)
  {
    return r.match(
        (E e) -> error(e),
        (V v) -> value(v)
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

Either类型,旨在密切反映Haskell的Either:

/**
 * A container for a disjunction of two possible types
 * By convention, the Left constructor is used to hold an error value and the Right constructor is used to hold a correct value
 * @param <L> The left alternative type
 * @param <R> The right alternative type
 */
public abstract class Either<L, R>
{
  public abstract <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf);

  public abstract void match(Consumer<? super L> lf, Consumer<? super R> rf);

  public <A, B> Either<A, B> bimap(Function<? super L, ? extends A> lf, Function<? super R, ? extends B> rf)
  {
    return match(
        (L l) -> left(lf.apply(l)),
        (R r) -> right(rf.apply(r))
    );
  }

  public L fromLeft(L left)
  {
    return match(
        (L l) -> l,
        (R r) -> left
    );
  }

  public R fromRight(R right)
  {
    return match(
        (L l) -> right,
        (R r) -> r
    );
  }

  public static <L, R> Either<L, R> left(L value)
  {
    return new Left<>(value);
  }

  public static <L, R> Either<L, R> right(R value)
  {
    return new Right<>(value);
  }

  private static <L, R> Either<L, R> cast(Either<? extends L, ? extends R> either)
  {
    return either.match(
        (L l) -> left(l),
        (R r) -> right(r)
    );
  }

  static class Left<L, R> extends Either<L, R>
  {
    final L value;

    Left(L value)
    {
      this.value = value;
    }

    @Override
    public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
    {
      return lf.apply(value);
    }

    @Override
    public void match(Consumer<? super L> lf, Consumer<? super R> rf)
    {
      lf.accept(value);
    }
  }

  static class Right<L, R> extends Either<L, R>
  {
    final R value;

    Right(R value)
    {
      this.value = value;
    }

    @Override
    public <T> T match(Function<? super L, ? extends T> lf, Function<? super R, ? extends T> rf)
    {
      return rf.apply(value);
    }

    @Override
    public void match(Consumer<? super L> lf, Consumer<? super R> rf)
    {
      rf.accept(value);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

示例用法

这个的主要用途是将异常抛出操作转换为monadic操作.这允许(检查)异常抛出方法在流和其他功能上下文中使用,并且还允许在返回类型上进行模式匹配和绑定.

private static void writeFiles(List<String> filenames, String content)
{
  filenames.stream()
      .map(
          (String s) -> Result.of(
              () -> new FileWriter(s) //Open file for writing
          ).peek(
              (FileWriter f) -> Result.of(
                  () -> f.write(content) //Write file contents
              )
          ).peek(
              (FileWriter f) -> Result.of(
                  () -> f.close()) //Close file
          )
      ).forEach(
          r -> r.match(
              (IOException e) -> System.out.println("exception writing to file: " + e), //Log exception
              (FileWriter f) -> System.out.println("successfully written to file '" + f + "'") //Log success
          )
      );

}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 2

只需使用接口履行契约的乐观假设,就像普通的 Java 代码总是这样做的那样(由编译器强制执行)。如果有人绕过此异常检查,则\xe2\x80\x99 不是你的责任来解决这个问题:

\n\n
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) {\n    try {\n        return value(v.get());\n    }\n    catch(RuntimeException|Error x) {\n        throw x; // unchecked throwables\n    }\n    catch(Exception ex) {\n        @SuppressWarnings("unchecked") E e = (E)ex;\n        return error(e);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,即使 Java 编程语言也同意可以继续进行此假设,例如

\n\n
public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v) throws E {\n    try {\n        return value(v.get());\n    }\n    catch(RuntimeException|Error x) {\n        throw x; // unchecked throwables\n    }\n    catch(Exception ex) {\n        throw ex; // can only be E\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

是有效的Java代码,因为在正常情况下,该get方法只能抛出或未经检查的可抛出对象,因此在已声明时,此处E重新抛出是有效的。当我们想要构造一个参数化的对象时,我们只需要规避 Java 语言的缺陷exthrows EResultE

\n