我在尝试Java 8的Lambda表达式时有一个问题。
通常它工作得很好,但现在我有了抛出IOException的方法。
最好看看下面的代码:
class Bank{
....
public Set<String> getActiveAccountNumbers() throws IOException {
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}
....
}
interface Account{
....
boolean isActive() throws IOException;
String getNumber() throws IOException;
....
}
问题是,它不能编译,因为我必须捕获isActive-和getNumber-Methods的可能异常。但是,即使我显式地使用如下所示的try-catch-Block,它仍然不能编译,因为我没有捕获异常。所以,要么是JDK有bug,要么是我不知道如何捕捉这些异常。
class Bank{
....
//Doesn't compile either
public Set<String> getActiveAccountNumbers() throws IOException {
try{
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}catch(IOException ex){
}
}
....
}
我怎样才能让它工作呢?谁能给我点提示吗?
Java中的功能接口不声明任何已检查或未检查的异常。
我们需要改变方法的签名:
boolean isActive() throws IOException;
String getNumber() throwsIOException;
To:
boolean isActive();
String getNumber();
或者用try-catch block处理它:
public Set<String> getActiveAccountNumbers() {
Stream<Account> s = accounts.values().stream();
s = s.filter(a ->
try{
a.isActive();
}catch(IOException e){
throw new RuntimeException(e);
}
);
Stream<String> ss = s.map(a ->
try{
a.getNumber();
}catch(IOException e){
throw new RuntimeException(e);
}
);
return ss.collect(Collectors.toSet());
}
另一种选择是编写自定义包装器或使用像ThrowingFunction这样的库。
使用库,我们只需要将依赖项添加到pom.xml:
<dependency>
<groupId>pl.touk</groupId>
<artifactId>throwing-function</artifactId>
<version>1.3</version>
</dependency>
并使用特定的类,如ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier。
代码的最后是这样的:
public Set<String> getActiveAccountNumbers() {
return accounts.values().stream()
.filter(ThrowingPredicate.unchecked(Account::isActive))
.map(ThrowingFunction.unchecked(Account::getNumber))
.collect(Collectors.toSet());
}
为了正确地添加IOException(到RuntimeException)处理代码,你的方法看起来像这样:
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> { try { return a.isActive(); }
catch (IOException e) { throw new RuntimeException(e); }});
Stream<String> ss = s.map(a -> { try { return a.getNumber() }
catch (IOException e) { throw new RuntimeException(e); }});
return ss.collect(Collectors.toSet());
现在的问题是,IOException必须被捕获为RuntimeException并转换回IOException——这将向上述方法添加更多代码。
为什么要使用Stream,因为它可以这样做——而且该方法会抛出IOException,所以也不需要额外的代码:
Set<String> set = new HashSet<>();
for(Account a: accounts.values()){
if(a.isActive()){
set.add(a.getNumber());
}
}
return set;
扩展@marcg解决方案,您通常可以在Streams中抛出和捕获检查异常;也就是说,编译器会要求你捕获/重新抛出,就像你在流外一样!
@FunctionalInterface
public interface Predicate_WithExceptions<T, E extends Exception> {
boolean test(T t) throws E;
}
/**
* .filter(rethrowPredicate(t -> t.isActive()))
*/
public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E {
return t -> {
try {
return predicate.test(t);
} catch (Exception exception) {
return throwActualException(exception);
}
};
}
@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwActualException(Exception exception) throws E {
throw (E) exception;
}
然后,您的示例将如下所示(添加测试以更清楚地显示它):
@Test
public void testPredicate() throws MyTestException {
List<String> nonEmptyStrings = Stream.of("ciao", "")
.filter(rethrowPredicate(s -> notEmpty(s)))
.collect(toList());
assertEquals(1, nonEmptyStrings.size());
assertEquals("ciao", nonEmptyStrings.get(0));
}
private class MyTestException extends Exception { }
private boolean notEmpty(String value) throws MyTestException {
if(value==null) {
throw new MyTestException();
}
return !value.isEmpty();
}
@Test
public void testPredicateRaisingException() throws MyTestException {
try {
Stream.of("ciao", null)
.filter(rethrowPredicate(s -> notEmpty(s)))
.collect(toList());
fail();
} catch (MyTestException e) {
//OK
}
}