Java 8 方法引用未处理的异常

2022-08-31 12:32:23

我正在使用Java 8进行项目,发现了一个我无法理解的情况。

我有这样的代码:

void deleteEntity(Node node) throws SomeException {
    for (ChildNode child: node.getChildren()) {
       deleteChild(child);
    }
}

void deleteChild(Object child) throws SomeException {
    //some code
}

此代码工作正常,但我可以使用方法引用重写它:

void deleteEntity(Node node) throws SomeException {
    node.getChildren().forEach(this::deleteChild);
}

并且此代码无法编译,因此给出错误 。Incompatible thrown types *SomeException* in method reference

IDEA还给了我错误。unhandled exception

所以,我的问题是为什么?为什么代码为每个循环编译,而不使用 lambda 编译?


答案 1

如果您查看 Consumer<T> 接口,该方法(这是您的方法引用将有效使用的)不会被声明为引发任何已检查的异常 - 因此您不能使用声明为引发已检查异常的方法引用。增强的 for 循环是可以的,因为在那里你总是处于一个可以抛出的上下文中。acceptSomeException

您可以创建一个包装器,它将选中的异常转换为未选中的异常,并引发该异常。或者,您可以使用一个确实会引发已检查异常的方法声明自己的函数接口(可能使用该异常参数化接口),然后编写自己的方法,将该函数接口作为输入。accept()forEach


答案 2

你可以试试这个:

void deleteEntity(Node node) throws SomeException {     node.getChildren().forEach(UtilException.rethrowConsumer(this::deleteChild));
    }

下面的帮助程序类允许您在 Java 流中使用任何已检查的异常。请注意,上面的流还抛出 由 引发的原始已检查异常,而不是一些包装未选中的异常。UtilExceptionthis::deleteChild

public final class UtilException {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

关于如何使用它的许多其他示例(静态导入后):UtilException

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }

但是,在了解以下优点,缺点和限制之前,请不要使用它

• 如果调用代码用于处理选中的异常,则必须将其添加到包含流的方法的 throw 子句中。编译器不会再强迫您添加它,因此更容易忘记它。

• 如果调用代码已经处理了已检查的异常,编译器将提醒您将 throws 子句添加到包含流的方法声明中(如果不这样做,它会说:异常永远不会在相应的 try 语句正文中引发)。

• 在任何情况下,您都无法围绕流本身来捕获包含流的方法内部的已检查异常(如果您尝试,编译器会说:异常永远不会在相应的 try 语句正文中引发)。

• 如果您调用的方法实际上永远不会引发它所声明的异常,则不应包含 throws 子句。例如:new String(byteArr, “UTF-8”) 抛出 UnsupportedEncodingException,但 Java 规范保证 UTF-8 始终存在。在这里,投掷声明是一种麻烦,任何以最小的样板来沉默它的解决方案都是受欢迎的。

如果你讨厌检查的异常,并且觉得它们不应该被添加到Java语言中(越来越多的人这样认为,我不是其中之一),那么就不要将选中的异常添加到包含流的方法的fores子句中。然后,已检查的异常的行为将与未选中的异常一样。

• 如果你正在实现一个严格的接口,其中你没有添加 throw 声明的选项,但抛出一个异常是完全合适的,那么包装一个异常只是为了获得抛出它的特权,会导致一个堆栈跟踪与虚假的异常,这些异常没有关于实际出错的信息。一个很好的例子是Runnable.run(),它不会引发任何检查的异常。在这种情况下,您可以决定不将已检验的异常添加到包含流的方法的 throw 子句中。

• 在任何情况下,如果您决定不将已检查的异常添加(或忘记添加)到包含流的方法的 throws 子句中,请注意引发 CHECKED 异常的以下 2 个后果:

1)调用代码将无法按名称捕获它(如果您尝试,编译器会说:异常永远不会在相应的try语句的正文中引发)。它会冒泡,并且可能被一些“catch Exception”或“catch Throwable”在主程序循环中被捕获,这可能是你想要的。

2)它违反了最小意外原则:捕获RunformException将不再足以保证捕获所有可能的异常。出于这个原因,我认为这不应该在框架代码中完成,而只能在你完全控制的业务代码中完成。

结论:我相信这里的限制并不严重,UtilException类可以毫无畏惧地使用。但是,这取决于你!


推荐