首页 > 解决方案 > 使用重新抛出异常的异常处理程序有什么明显的区别吗

问题描述

给定一个可能抛出的函数:

public static int f() throws Exception {
    // do something
}

这段代码有什么办法:

public static int catchF() throws Exception {
    try {
        return f();
    } catch (Exception ex) {
        throw ex;
    }
}

和直接打电话有什么区别f?即调用者可以通过检查异常来检测差异吗?catchF使用而不是有任何明显的开销f吗?

如果没有区别,编译器或 JVM 是否可以将调用优化catchF为直接调用f

虽然这似乎是一件奇怪的事情,但用例是在之前隐藏它之后在类型级别重新引入异常:

class Test {

    // Hide the exception.
    public static <X extends Exception, T> T throwUnchecked(Exception ex) throws X {
        throw (X) ex;
    }

    // Interface for functions which throw.
    interface Throws<T, R, X extends Exception> {
        R apply(T t) throws X;
    }


    // Convert a function which throws a visible exception into one that throws a hidden exception.
    public static <T, R, X extends Exception> Function<T, R> wrap(Throws<T, R, X> thrower) {
        return t -> {
            try {
                return thrower.apply(t);
            } catch(Exception ex) {
                return throwUnchecked(ex);
            }
        };
    }

    // Unhide an exception.
    public static <R, X extends Exception> R unwrap(Supplier<R> supp) throws X {
        try {
            return supp.get();
        } catch (Exception ex) {
            throw (X)ex;
        }
    }

    public static Stream<Integer> test(Stream<String> ss) throws NumberFormatException {
        return Test.<Stream<Integer>, NumberFormatException>unwrap(
                () -> ss.map(wrap(Integer::parseInt))
        );
    }

    public static void main(String[] args) throws NumberFormatException {
        final List<Integer> li = test(Arrays.stream(new String[]{"1", "2", "3"})).collect(toList());
        System.out.println(li);
    }
}

目的是将抛出异常的函数包装到在类型级别隐藏异常的函数中。这使得异常可用于例如流。

标签: javaexceptionoptimization

解决方案


与直接调用 f 有什么不同?

不。

即调用者可以通过检查异常来检测差异吗?

不,因为此时您没有构建新的异常。堆栈跟踪是在new WhateverException(...)调用 the 的地方构建的(不是在哪里throw,尽管它们通常在同一个地方)。

通常,如果由于异常而需要进行一些清理,则重新抛出捕获的异常:

try {
  // ...
} catch (SomeException e) {
  // Clean up resources.
  throw e;
}

调用堆栈展开时发生的事情对调用者既不可见也不相关。

一个快速演示可以显示堆栈跟踪是相同的,无论异常是被捕获并重新抛出还是仅仅允许传播

使用 catchF 而不是 f 是否有任何明显的开销?

构造异常的开销将远远超过这个冗余构造的任何开销。


推荐阅读