对于你的问题,简单的答案是:你不能,至少不能直接。这不是你的错。甲骨文搞砸了。他们坚持受控异常的概念,但在设计功能接口、流、lambda等时却总是忘记了受控异常。这对罗伯特·c·马丁(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()抛出E, Stream.map()也会抛出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需要解析为ClassNotFoundException和IOException的公共超,即Exception。
需要对类型推断的定义进行调整,以便在允许使用类型列表(throws子句)的地方使用类型形参时,编译器可以查找多个类型。然后,编译器报告的异常类型将与引用方法的已检查异常的原始throws声明一样具体,而不是单一的全能超类型。
坏消息是,这意味着甲骨文搞砸了。当然,它们不会破坏用户土地代码,但是向现有功能接口引入异常类型参数会破坏显式使用这些接口的所有用户土地代码的编译。他们必须发明一些新的语法来解决这个问题。
更糟糕的消息是Brian Goetz在2010年就已经讨论过这个话题了(https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java, http://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html),但我被告知这个调查最终没有成功,而且据我所知,Oracle目前没有减轻受控异常和lambdas之间交互的工作。