我在尝试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){
        }
    }
    ....
}

我怎样才能让它工作呢?谁能给我点提示吗?


当前回答

你的例子可以写成:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Unthrow类可以在这里https://github.com/SeregaLBN/StreamUnthrower

其他回答

如果你不介意使用第三方库,AOL的cyclops-react库,disclosure::我是一个贡献者,有一个ExceptionSoftener类可以在这里提供帮助。

 s.filter(softenPredicate(a->a.isActive()));

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());
}

使用#propagate()方法。来自Sam Beran的Java 8 Blog的非guava实现示例:

public class Throwables {
    public interface ExceptionWrapper<E> {
        E wrap(Exception e);
    }

    public static <T> T propagate(Callable<T> callable) throws RuntimeException {
        return propagate(callable, RuntimeException::new);
    }

    public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw wrapper.wrap(e);
        }
    }
}

你的例子可以写成:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Unthrow类可以在这里https://github.com/SeregaLBN/StreamUnthrower

为了正确地添加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;