我在尝试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){
}
}
....
}
我怎样才能让它工作呢?谁能给我点提示吗?
为了正确地添加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;
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());
}
也可以使用一些外部(流)错误指示器在更高级别抛出异常:
List<String> errorMessages = new ArrayList<>(); // error indicator
//..
errorMessages.clear();
List<String> names = new ArrayList<>(Arrays.asList("andrey", "angela", "pamela"));
names.stream()
.map(name -> {
if (name != "pamela") {
errorMessages.add(name + " is wrong here!");
return null; // triggering the indicator
}
return name;
} )
.filter(elem -> (elem != null)) // bypassing propagation of only current unwanted data
//.filter(elem -> (errorMessages.size() == 0)) // or blocking any propagation once unwanted data detected
.forEach(System.out::println);
if (errorMessages.size() > 0) { // handling the indicator
throw new RuntimeException(String,join(", ", errorMessages));
}
如果您希望处理流中的异常并继续处理其他异常,那么Brian Vermeer在DZone中有一篇使用Either概念的优秀文章。这是一种处理这种情况的好方法。唯一缺少的是示例代码。这是我使用那篇文章中的概念进行探索的一个示例。
@Test
public void whenValuePrinted_thenPrintValue() {
List<Integer> intStream = Arrays.asList(0, 1, 2, 3, 4, 5, 6);
intStream.stream().map(Either.liftWithValue(item -> doSomething(item)))
.map(item -> item.isLeft() ? item.getLeft() : item.getRight())
.flatMap(o -> {
System.out.println(o);
return o.isPresent() ? Stream.of(o.get()) : Stream.empty();
})
.forEach(System.out::println);
}
private Object doSomething(Integer item) throws Exception {
if (item == 0) {
throw new Exception("Zero ain't a number!");
} else if (item == 4) {
return Optional.empty();
}
return item;
}