一尘不染

如何从Java 8流中引发CHECKED异常?

java

如何从Java 8流/ lambda中抛出CHECKED异常?

换句话说,我想使代码像这样编译:

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

由于Class.forName()上面的方法throws ClassNotFoundException,因此该代码无法编译。

请注意,我不想将已检查的异常包装在运行时异常中,而是将已包装的未检查的异常抛出。我想抛出被检查的异常本身,而又不添加丑陋的try/ catches流。


阅读 443

收藏
2020-03-02

共2个答案

一尘不染

LambdaExceptionUtil帮助程序类使你可以在Java流中使用任何已检查的异常,如下所示:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

注意Class::forName抛出异常ClassNotFoundException,已被选中。流本身也会抛出ClassNotFoundException,而不是一些包装未检查的异常。

public final class LambdaExceptionUtil {

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

}

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

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

注意1:可以使用上述类的rethrow方法LambdaExceptionUtil而不必担心,在任何情况下都可以使用。非常感谢帮助解决最后一个问题的用户@PaoloC:现在,编译器将要求你添加throw子句,一切都好像你可以在Java 8流上本地抛出检查异常一样。

注意2:uncheck上述LambdaExceptionUtil类的方法是奖励方法,如果你不想使用它们,可以将其安全地从类中删除。如果你确实使用过它们,请谨慎操作,而不要先了解以下用例,优点/缺点和限制:

uncheck如果要调用一个从字面上永远不会引发其声明的异常的方法,则可以使用这些方法。例如:new String(byteArr,“ UTF-8”)引发UnsupportedEncodingException,但是Java规范保证UTF-8始终存在。在这里,throws声明是个麻烦事,欢迎使用最小的样板来使它静音的任何解决方案:String text = uncheck(() -> new String(byteArr, "UTF-8"));

• uncheck如果要实现一个严格的接口,而该接口没有添加throws声明的选项,但是抛出异常是完全适当的,则可以使用这些方法。包装异常只是为了获得抛出异常的特权,会导致产生带有虚假异常的堆栈跟踪,这些异常不会提供有关实际出了什么问题的信息。一个很好的例子是Runnable.run(),它不会引发任何检查的异常。

•无论如何,如果你决定使用这些uncheck方法,请注意在没有throws子句的情况下抛出CHECKED异常的两种后果:1)调用代码将无法按名称捕获(如果尝试,则编译器会说:永远不会在相应的try语句的主体中引发异常)。它会冒泡,并且可能会被某些“ catch Exception”或“ catch Throwable”捕获在主程序循环中,这可能还是你想要的。2)它违反了最少惊讶的原则:它将不再足以RuntimeException确保能够捕获所有可能的异常。因此,我认为这不应在框架代码中完成,而应在完全控制的业务代码中完成。

2020-03-02
一尘不染

你问题的简单答案是:你不能,至少不能直接这样做。这不是你的错。甲骨文搞砸了。他们坚持使用检查异常的概念,但是在设计功能接口,流,lambda等时,始终不一致地忘记照顾检查异常。这对于像Robert C. Martin这样的专家来说都是很明智的,他们将检查异常称为失败的实验。

在我看来,这是一个巨大的错误中的API,并在一个小错误语言规范。

API中的错误是它没有提供转发检查的异常的功能,而这实际上会使功能编程变得非常有意义。正如我将在下面演示的那样,这样的功能将很容易实现。

语言规范中的错误是,它仅允许在允许使用类型列表(throws子句)的情况下使用类型参数而不是单个类型来推断类型列表。

作为Java程序员,我们期望可以编译以下代码:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

但是,它给出:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

其中功能接口的定义方式目前防止编译器转发例外-有没有宣言,告诉Stream.map(),如果Function.apply() throws E,Stream.map() throws E以及。

缺少的是用于通过检查的异常传递的类型参数的声明。以下代码显示了如何实际上可以使用当前语法声明这种直通类型参数。除了标记行中的特殊情况(这是下面讨论的限制)之外,此代码将编译并按预期运行。

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

throwSomeMore我们而言,我们希望看到IOException它被错过了,但实际上却错过了Exception

这不是完美的,因为即使在出现异常的情况下,类型推断似乎仍在寻找单个类型。因为类型推断需要一个单一的类型,E需要决心共同superClassNotFoundExceptionIOException,这是Exception

需要对类型推断的定义进行一些调整,以便在允许使用类型列表(throws子句)的地方使用类型参数时,编译器将查找多个类型。然后,编译器报告的异常类型将throws与所引用方法的已检查异常的原始声明一样具体,而不是单个“包罗万象”超级类型。

坏消息是,这意味着Oracle搞砸了。当然,它们不会破坏用户区代码,但是在现有功能接口中引入异常类型参数将破坏所有显式使用这些接口的用户区代码的编译。他们必须发明一些新的语法糖来解决此问题。

2020-03-02