簡體   English   中英

捕獲一般異常

[英]Catching a generic exception

問題

我在Java中編寫Result類型,我發現需要它有一個方法來執行可能失敗的操作,然后在新的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);
    }
  }
  ...
}

但Java無法捕獲由類型參數定義的異常。 我也嘗試過使用instanceof ,但也不能用於泛型。 有什么方法可以實現這個方法嗎?

定義

這是另外的在我的結果類型of方法。 它的目的是類似於Haskell的Eitherrust的Result ,同時還具有有意義的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)
    );
  }
}

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

示例用法

這個的主要用途是將異常拋出操作轉換為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
          )
      );

}

您需要訪問異常的類,然后在catch塊中使用一些泛型。

一種簡單的方法是將Class<E>類傳遞給Result.of方法:

public static <E extends Throwable, V> Result<E, V> of(
        ThrowingSupplier<V, E> v,
        Class<E> errorType) {

    try {
        return value(v.get());
    } catch(Throwable e) {
        if (errorType.isInstance(e)) {
            return error(errorType.cast(e));
        }
        throw new RuntimeException(e); // rethrow as runtime?
    }
}

用法:

Result.of(() -> new FileWriter(s), IOException.class)

Class.isInstance是動態等效instanceof靜態操作,而Class.cast是一樣的靜態鑄造: (E) e ,但我們沒有得到來自編譯器警告。


編輯:當捕獲的Throwable不是您期望的異常類型時,您需要考慮該怎么做。 我把它包裝在RuntimeException並重新RuntimeException它。 這允許繼續為monad使用流暢的樣式,但不再透明,因為現在任何異常都包含在未經檢查的異常中。 也許你可以在Result.of添加第三個參數來處理這個特定的情況......

更新:這似乎根本不起作用。 我現在暫時保留它,因為我鏈接到其他地方,因為它使用了其他公認答案中提供的方法,我想繼續調查。


使用Federico的答案和評論中鏈接的答案 ,我推斷出一個與原始問題具有相同方法簽名的解決方案,並且我創建了一個封裝此功能以供將來使用的類。

Result實施:

public class Result<E extends Exception, V>
{
  ...
  public static <E extends Exception, V> Result<E, V> of(ThrowingSupplier<V, E> v)
  {
    try
    {
      return value(v.get());
    }
    catch(Exception e)
    {
      Class<E> errType = Reflector.getType();
      if (errType.isInstance(e))
      {
        return error(errType.cast(e));
      }
      else
      {
        throw (RuntimeException) e;
      }
    }
  }
  ...
}

Reflector

import java.lang.reflect.ParameterizedType;

/**
 * This class only exists to provide a generic superclass to {@link Reflector}
 * @param <E> The type for the subclass to inspect
 */
abstract class Reflected<E>
{ }

/**
 * This class provides the ability to obtain information about its generic type parameter.
 * @param <E> The type to inspect
 * @author
 */
@Deprecated
public class Reflector<E> extends Reflected<E>
{
  /**
   * Returns the class corresponding to the type {@code <E>}.
   * @param <E> The type to inspect
   * @return The class corresponding to the type {@code <E>}
   */
  public static <E> Class<E> getType()
  {
    return new Reflector<E>().getParameterType();
  }

  private Reflector() {}
  private Class<E> getParameterType()
  {
    final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
    return (Class<E>) type.getActualTypeArguments()[0];
  }
}

只需使用接口履行合同的樂觀假設,因為普通的Java代碼將始終如此(由編譯器強制執行)。 如果有人繞過此異常檢查,則您無需解決此問題:

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

請注意,即使Java編程語言也同意可以繼續這種假設,例如

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

是有效的Java代碼,因為在正常情況下, get方法只能拋出E或未經檢查的throwable,所以當throws E已經聲明時,重新拋出ex在這里是有效的。 當我們想要構造一個用E參數化的Result時,我們只需要避免Java語言的缺陷。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM