如何从Java8 streams/lambdas内部抛出已检查的异常
换句话说,我想编译如下代码:
公共列表<;类别>;getClasses()抛出ClassNotFoundException{
列出<;类别>;类别=
Stream.of(“java.lang.Object”、“java.lang.Integer”、“java.lang.String”)
.map(className->;Class.forName(className))
.collect(Collectors.toList());
返回类;
}
此代码不会编译,因为上面的Class.forName()方法会抛出ClassNotFoundException,这是选中的
请注意,我不想将选中的异常包装到运行时异常中,而是抛出包装好的未选中异常我希望抛出选中的异常本身,并且在不向流中添加丑陋的的情况下,尝试/捕获
你的问题的简单答案是:你不能,至少不能直接回答。这不是你的错Oracle把它搞砸了。他们坚持检查异常的概念,但在设计功能接口、流、lambda等时却始终忘了注意检查异常。这是像Robert C.Martin这样的专家们最担心的问题,他们称检查异常为失败的实验
在我看来,这在API中是一个巨大的错误,在语言规范中是一个小错误
API中的错误在于,它没有提供转发已检查异常的功能,而这实际上对函数式编程非常有意义。正如我将在下面演示的那样,这样的设施是很容易实现的
语言规范中的缺陷在于,它不允许类型参数推断类型列表而不是单个类型,只要类型参数仅在允许类型列表的情况下使用(throws子句)
作为Java程序员,我们期望编译以下代码:
导入java.util.ArrayList;
导入java.util.List;
导入java.util.stream.stream;
公共类CheckedStream{
//列出变体以演示重构之前我们实际拥有的内容。
public List<;Class>;getClasses(最终列表<;String>;名称)引发ClassNotFoundException{
最终列表<;类>;类=新阵列列表<;>;();
for(最终字符串名称:名称)
Class.add(Class.forName(name));
返回类;
}
//我们要编译的流函数。
公共流<;Class>;GetClass(最终流<;字符串>;名称)引发ClassNotFoundException{
返回names.map(类::forName);
}
}
然而,它给出了:
[email protected]:~/playerd/Java/checkedStream$javac checkedStream.Java
java:13:错误:方法引用中不兼容的抛出类型ClassNotFoundException
返回names.map(类::forName);
^
1错误
函数接口的定义方式目前阻止编译器转发异常-没有任何声明会告诉Stream.map(),如果Function.apply()抛出E,Stream.map()也抛出E
缺少的是用于传递已检查异常的类型参数声明。下面的代码显示了如何使用当前语法声明这样的传递类型参数。除了标记行中的特殊情况(这是下面讨论的限制)之外,此代码的编译和行为符合预期
导入java.io.IOException;
接口功能<;T、 R、E扩展可丢弃性>;{
//声明你扔了E,不管是什么。
R应用(T)抛出E;
}
接口流<;T>;{
//通过E,无论为E定义了什么映射器。
<;R,E扩展可丢弃>;流<;R>;映射(函数<;?超级T,?扩展R,E>;映射器)抛出E;
}
班长{
公共静态void main(最终字符串…args)引发ClassNotFoundException{
最终流<;字符串>;s=null;
//作品:E是ClassNotFoundException。
s、 映射(类::forName);
//Works:E是运行时异常(可能)。
s、 映射(Main::convertClass);
//作品:E是ClassNotFoundException。
s、 映射(Main::throwSome);
//不起作用:E是例外。
s、 map(Main::throwSomeMore);//错误:未报告的异常;必须捕获或声明要抛出
}
公共静态类convertClass(最终字符串s){
返回Main.class;
}
静态类FooException扩展ClassNotFoundException{}
静态类BarException扩展ClassNotFoundException{}
公共静态类throwSome(最终字符串s)抛出FooException、BarException{
抛出新的FooException();
}
公共静态类throwSomeMore(最终字符串s)抛出ClassNotFoundException、IOException{
抛出新的FooException();
}
}
在throwSomeMore的情况下,我们希望看到IOException被忽略,但实际上它忽略了Exception
这并不完美,因为类型推断似乎只寻找一个类型,即使在异常的情况下也是如此。由于类型推断需要单个类型,E需要解析为ClassNotFoundException和IOException的公共super,即Exception
需要对类型推断的定义进行调整,以便如果在允许类型列表的情况下使用类型参数(throws子句),编译器将查找多个类型。然后,编译器报告的异常类型将与引用方法的已检查异常的原始抛出声明一样具体,而不是单一的catch-all超类型
坏消息是,这意味着甲骨文把事情搞砸了。当然,它们不会破坏用户区代码,但向现有功能接口引入异常类型参数将破坏显式使用这些接口的所有用户区代码的编译。他们必须发明一些新的语法来解决这个问题
更糟糕的是,布莱恩·戈茨(Brian Goetz)在2010年已经讨论过这个话题https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java“rel=”noreferrer“>https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java(新链接:http://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html)但我被告知,这项调查最终没有成功,而且据我所知,Oracle目前没有任何工作可以缓解已检查异常和lambdas之间的交互