我如何从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。
只要使用Lombok的@SneakyThrows就可以了。
Christian Hujer已经详细解释了为什么严格来说,由于Java的限制,从流中抛出受控异常是不可能的。
其他一些回答解释了一些技巧,可以绕过语言的限制,但仍然能够满足抛出“检查异常本身,并且不向流中添加丑陋的try/catch”的要求,其中一些需要额外的数十行样板文件。
我要强调的是另一个选项,在我看来,这个选项比其他选项干净得多:Lombok的@SneakyThrows。这个问题在其他答案中也提到过,但有很多不必要的细节。
生成的代码非常简单:
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(className -> getClass(className))
.collect(Collectors.toList());
return classes;
}
@SneakyThrows // <= this is the only new code
private Class<?> getClass(String className) {
return Class.forName(className);
}
我们只需要一个提取方法重构(由IDE完成)和一行额外的@SneakyThrows。注释负责添加所有样板文件,以确保您可以抛出检查过的异常,而无需将其包装在RuntimeException中,也无需显式声明它。
这样做是不安全的。你可以作弊,但你的程序坏了,这将不可避免地反过来伤害别人(应该是你,但我们的作弊经常会伤害到别人)。
这里有一个稍微安全一点的方法(但我仍然不推荐这样做)。
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中的异常,从流管道中抛出一个信号,表明计算异常失败,捕获信号,并对该信号进行操作以抛出底层异常。关键是您总是捕获合成异常,而不是在没有声明抛出异常的情况下允许已检查异常泄漏。
也许,更好、更实用的方法是包装异常并在流中进一步传播它们。以Vavr的Try类型为例。
例子:
interface CheckedFunction<I, O> {
O apply(I i) throws Exception; }
static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
return i -> {
try {
return f.apply(i);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
} }
fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))
OR
@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
throw (E) e;
}
static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
return arg -> {
try {
return f.apply(arg);
} catch(Exception ex) {
return throwUnchecked(ex);
}
};
}
第二个实现避免将异常包装在RuntimeException中。throwUnchecked可以工作,因为在java中几乎所有泛型异常都被视为未检查的。
你不能。
然而,你可能想要看看我的一个项目,它可以让你更容易地操纵这种“投掷lambda”。
在你的情况下,你可以这样做:
import static com.github.fge.lambdas.functions.Functions.wrap;
final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(f.orThrow(MyException.class))
.collect(Collectors.toList());
并捕获MyException。
这是一个例子。另一个例子是你可以. orreturn()一些默认值。
请注意,这仍然是一项正在进行的工作,更多的是来。更好的名字,更多的功能等等。