在 Java 中捕获通用异常?

问题描述 投票:0回答:8

我们在工作中使用 JUnit 3,并且没有

ExpectedException
注释。我想在我们的代码中添加一个实用程序来包装它:

 try {
     someCode();
     fail("some error message");
 } catch (SomeSpecificExceptionType ex) {
 }

所以我尝试了这个:

public static class ExpectedExceptionUtility {
  public static <T extends Exception> void checkForExpectedException(String message, ExpectedExceptionBlock<T> block) {
     try {
        block.exceptionThrowingCode();
        fail(message);
    } catch (T ex) {
    }
  }
}

但是,我认为 Java 不能在 catch 块中使用通用异常类型。

我怎样才能做这样的事情,绕过 Java 的限制?

有没有办法检查

ex
变量是否属于
T
类型?

java generics exception
8个回答
38
投票

您可以传入 Class 对象并以编程方式检查它。

public static <T extends Exception> void checkForException(String message, 
        Class<T> exceptionType, ExpectedExceptionBlock<T> block) {
    try {
       block.exceptionThrowingCode();
   } catch (Exception ex) {
       if ( exceptionType.isInstance(ex) ) {
           return;
       } else {
          throw ex;  //optional?
       }
   }
   fail(message);
}

//...
checkForException("Expected an NPE", NullPointerException.class, //...

我不确定您是否想要重新抛出;重新抛出同样会使测试失败/出错,但从语义上讲我不会,因为它基本上意味着“我们没有得到我们期望的异常”,因此这代表了编程错误,而不是测试环境错误。


4
投票

我理解尝试简化异常测试习惯用法的冲动,但说真的:不要这样做。 你想到的每一个可能的选择都是比疾病更糟糕的治疗方法。 特别是 JUnit 4 的 @ExpectedException 胡说八道! 这是一个过于聪明的框架解决方案,要求每个人都了解它是如何工作的,而不是简单的、不言而喻的常规 Java 代码。更糟糕的是,它使您无法仅包装您期望抛出异常的测试部分,因此,如果较早的设置步骤抛出相同的异常,即使您的代码已损坏,您的测试也会通过。

我可以在这里写一篇关于这个问题的长篇谩骂(很抱歉没有足够的时间),因为我们在 Google 的 Java 工程师中对这个问题进行了长时间的讨论,并且一致认为这些疯狂的事情都不是解决方案是值得的。 习惯尝试/捕捉,其实没那么糟糕。


2
投票

带有类型参数的 Catch 子句是不可能的:
http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#cannotCatch


0
投票

好吧,如果不是预期的异常,您可以捕获异常并重新抛出。尽管良好的编码实践通常规定代码的成功路径不应由异常定义,因此您可能需要重新考虑您的设计。


0
投票

泛型不是类型。它们不是模板。它们是 Java 中的编译时类型检查。异常块捕获类型。您可以 catch(Exception e) 甚至 catch(Throwable e),然后根据需要进行强制转换。


0
投票

您还可以使用支持实时模板的 IDE(例如 IntellJ IDEA )并分配一个像

ee -> [tab]
这样的快捷方式,为您插入 try/catch/ignore 并让您输入正确的内容。

like this

like this


0
投票
@FunctionalInterface
 public interface ThrowingConsumer<T, E extends Exception> {
      void accept(T t) throws E;
 }
public static <T, E extends Exception> Consumer<T> errConsumerWrapper(ThrowingConsumer<T, E> throwingConsumer,
                   Class<E> exceptionClass, 
                   Consumer<E> exceptionConsumer) {
            return i -> {
                try {
                    throwingConsumer.accept(i);
                } catch (Exception ex) {
                    try {
                        exceptionConsumer.accept(exceptionClass.cast(ex));
                    } catch (ClassCastException ccEx) {
                        throw new RuntimeException(ex);
                    }
                }
            };
        }
  1. 使用示例

    Stream.of("a").forEach(errConsumerWrapper(i -> Integer.parseInt(i), NumberFormatException.class, 可抛出::printStackTrace));


0
投票

如果您对 OOP+FP 解决此问题的方式感兴趣,有一种特定类型

Either<L, R>
,其中可以通过
Either<L extends Throwable, R>
方法将专业化创建为
.tryCatch()

虽然在这个答案中展示

Either<L, R>
的整个实现有点太多(完整记录的实现位于这个Gist中),但下面是一个释义快照,说明了具体化
Throwable
的任何后代的样子进入适当的捕获过滤器。

要查看实现细节,请特别关注这对

tryCatch()
方法。这个 StackOverflow Answer 启发了这两种方法中的第一种。

public final class Either<L, R> {
  public static <L, R> Either<L, R> left(L value) {
    return new Either<>(Optional.of(value), Optional.empty());
  }
  public static <L, R> Either<L, R> right(R value) {
    return new Either<>(Optional.empty(), Optional.of(value));
  }

  public static <L extends Throwable, R> Either<L, R> tryCatch(
      Supplier<R> successSupplier,
      Class<L> throwableType
  ) {
    try {
      return Either.right(Objects.requireNonNull(successSupplier.get()));
    } catch (Throwable throwable) {
      if (throwableType.isInstance(throwable)) {
        return Either.left((L) throwable);
      }

      throw throwable;
    }
  }

  public static <R> Either<RuntimeException, R> tryCatch(Supplier<R> successSupplier) {
    return tryCatch(successSupplier, RuntimeException.class);
  }

  private final Optional<L> left;
  private final Optional<R> right;

  private Either(Optional<L> left, Optional<R> right) {
    if (left.isEmpty() == right.isEmpty()) {
      throw new IllegalArgumentException("left.isEmpty() must not be equal to right.isEmpty()");
    }
    this.left = left;
    this.right = right;
  }

  @Override
  public boolean equals(Object object) { ... }
  @Override
  public int hashCode() { ... }
  public boolean isLeft() { ... }
  public boolean isRight() { ... }
  public L getLeft() { ... }
  public R getRight() { ... }
  public Optional<R> toOptional() { ... }
  public <T> Either<L, T> map(Function<? super R, ? extends T> rightFunction) { ... }
  public <T> Either<L, T> flatMap(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
  public <T> Either<T, R> mapLeft(Function<? super L, ? extends T> leftFunction) { ... }
  public <T> Either<L, T> mapRight(Function<? super R, ? extends T> rightFunction) { ... }
  public <T> Either<T, R> flatMapLeft(Function<? super L, ? extends Either<? extends T, R>> leftFunction) { ... }
  public <T> Either<L, T> flatMapRight(Function<? super R, ? extends Either<L, ? extends T>> rightFunction) { ... }
  public <T> T converge(Function<? super L, ? extends T> leftFunction, Function<? super R, ? extends T> rightFunction) { ... }
  public <T> T converge() { ... }
  public static <T> T converge(Either<? extends T, ? extends T> either) { ... }
  public void forEach(Consumer<? super L> leftAction, Consumer<? super R> rightAction) { ... }
}

查看上述代码实际运行的一种快速方法是查看 JUnit 测试的验证suite

© www.soinside.com 2019 - 2024. All rights reserved.