我如何从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()方法会抛出ClassNotFoundException,该异常会被检查。

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


当前回答

这样做是不安全的。你可以作弊,但你的程序坏了,这将不可避免地反过来伤害别人(应该是你,但我们的作弊经常会伤害到别人)。

这里有一个稍微安全一点的方法(但我仍然不推荐这样做)。

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

在这里,您要做的是捕获lambda中的异常,从流管道中抛出一个信号,表明计算异常失败,捕获信号,并对该信号进行操作以抛出底层异常。关键是您总是捕获合成异常,而不是在没有声明抛出异常的情况下允许已检查异常泄漏。

其他回答

这个答案类似于17,但避免了包装器异常定义:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }

我写了一个扩展Stream API的库,以允许抛出受控异常。它使用了Brian Goetz的技巧。

您的代码将变成

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}

您还可以编写一个包装器方法来包装未检查的异常,甚至使用表示另一个功能接口的附加参数(具有相同的返回类型R)来增强包装器。 在这种情况下,您可以传递一个函数,该函数将在异常情况下执行并返回。 请看下面的例子:

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

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

我认为这种方法是正确的:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

将检查过的异常包装在一个unclaredthrowableexception(这是该异常的用例)的Callable内部,并在外部将其展开。

是的,我觉得它很难看,我建议在这种情况下不要使用lambdas,而是退回到一个好的旧循环,除非您正在使用并行流,并且并行化带来了客观的好处,可以证明代码的不可读性是正确的。

正如许多人所指出的那样,有解决这种情况的解决方案,我希望其中一个解决方案能够出现在Java的未来版本中。

我使用这种包装异常:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

它需要静态处理这些异常:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

在网上试试!

尽管在第一次rethrow()调用期间无论如何都会重新抛出异常(哦,Java泛型…),这种方式允许获得可能异常的严格静态定义(需要在抛出中声明它们)。不需要instanceof或其他东西。