Java 8:Lambda-Streams,按方法过滤,具有异常

问题

我在尝试Java 8的Lambda表达式时遇到问题。通常它工作正常,但现在我有方法throwIOException。最好看一下以下代码:

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中存在错误,要么我不知道如何捕获这些异常。

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){
        }
    }
    ....
}

我怎样才能让它发挥作用?有人能暗示我找到正确的解决方案吗?


#1 热门回答(176 赞)

你必须抓住异常逃脱lambda:

s = s.filter(a -> { try { return a.isActive(); } 
                    catch (IOException e) { throw new UncheckedIOException(e); }}});

考虑一下这样一个事实:lambda不会在你编写它的地方进行评估,而是在JDK类中的一些完全不相关的地方进行评估。因此,这将是抛出已检查异常的点,并且在该位置不会声明它。

你可以使用lambda的包装器来处理它,该包装器将已检查的异常转换为未经检查的异常:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (RuntimeException e) { throw e; }
  catch (Exception e) { throw new RuntimeException(e); }
}

你的例子将写成

return s.filter(a -> uncheckCall(a::isActive))
        .map(Account::getNumber)
        .collect(toSet());

在我的项目中,我没有包装就处理这个问题;相反,我使用的方法有效地解除了编译器对异常的检查。毋庸置疑,应谨慎处理,项目中的每个人都必须意识到,未声明的情况下可能会出现已检查的异常。这是管道代码:

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (Exception e) { return sneakyThrow(e); }
}
public static void uncheckRun(RunnableExc r) {
  try { r.run(); } catch (Exception e) { sneakyThrow(e); }
}
public interface RunnableExc { void run() throws Exception; }


@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

即使collect没有声明,你也可以期待得到IOException。无论如何,你想要重新抛出异常,而不是所有现实生活中的情况,并将其作为一般失败处理。在所有这些情况下,在清晰度或正确性方面没有任何损失。只要注意其他情况,你实际上想要在现场对异常作出反应。开发人员不会被编译器意识到有一个IOException可以捕获它并且编译器实际上会抱怨如果你试图捕获它,因为我们已经愚弄它相信不会抛出这样的异常。


#2 热门回答(23 赞)

你也可以使用lambdas传播你的静态疼痛,所以整个事情看起来可读:

s.filter(a -> propagate(a::isActive))

propagate这里接收java.util.concurrent.Callable作为参数并将调用期间捕获的任何异常转换为RuntimeException。番石榴有类似的转换方法Throwables#propagate(Throwable)

这种方法对于lambda方法链接似乎是必不可少的,所以我希望有一天它会被添加到一个流行的lib中,或者这种传播行为将是默认的。

public class PropagateExceptionsSample {
    // a simplified version of Throwables#propagate
    public static RuntimeException runtime(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException)e;
        }

        return new RuntimeException(e);
    }

    // this is a new one, n/a in public libs
    // Callable just suits as a functional interface in JDK throwing Exception 
    public static <V> V propagate(Callable<V> callable){
        try {
            return callable.call();
        } catch (Exception e) {
            throw runtime(e);
        }
    }

    public static void main(String[] args) {
        class Account{
            String name;    
            Account(String name) { this.name = name;}

            public boolean isActive() throws IOException {
                return name.startsWith("a");
            }
        }


        List<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela")));

        Stream<Account> s = accounts.stream();

        s
          .filter(a -> propagate(a::isActive))
          .map(a -> a.name)
          .forEach(System.out::println);
    }
}

#3 热门回答(18 赞)

ThisUtilExceptionhelper类允许你在Java流中使用任何已检查的异常,如下所示:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

NoteClass::forNamethrowsClassNotFoundException,其中已检查.流本身也是throwsClassNotFoundException,而不是一些包装未经检查的异常。

public final class UtilException {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/**.forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/**.map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/**rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/**uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/**uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/**uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

关于如何使用它的许多其他示例(在静态导入UtilException之后):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }

但是在理解以下优点,缺点和限制之前不要使用它

•如果调用代码要处理已检查的异常,则必须将其添加到包含该流的方法的throws子句中。编译器不会强迫你再添加它,因此更容易忘记它。

•如果调用代码已经处理了已检查的异常,编译器将提醒你将throws子句添加到包含该流的方法声明中(如果不这样做,则会说:异常永远不会在相应的try语句的主体中抛出)。

•在任何情况下,你将无法包围流本身以捕获已检查的异常INSIDE包含流的方法(如果你尝试,编译器将说:异常永远不会在相应的try语句的主体中抛出)。

•如果你正在调用一个字面上永远不会抛出它声明的异常的方法,那么你不应该包含throws子句。例如:new String(byteArr,"UTF-8")抛出UnsupportedEncodingException,但Java规范保证UTF-8始终存在。在这里,投掷声明是一个麻烦,任何解决方案,以最小的样板沉默它是受欢迎的。

•如果你讨厌检查异常并觉得它们永远不应该被添加到Java语言中(越来越多的人这样认为,而我不是其中之一),那么就不要将已检查的异常添加到throws包含流的方法的子句。然后,检查的异常将表现得像UNchecked异常。

•如果你正在实现一个严格的接口,在那里你没有添加throws声明的选项,但抛出异常是完全合适的,那么为了获得抛出它的特权而包装异常会导致带有虚假异常的堆栈跟踪它没有提供关于实际出错的信息。一个很好的例子是Runnable.run(),它不会抛出任何已检查的异常。在这种情况下,你可以决定不将已检查的异常添加到包含该流的方法的throws子句中。

•在任何情况下,如果你决定不将(但忘记添加)已检查的异常添加到包含流的方法的throws子句中,请注意抛出CHECKED异常的这两个后果:

1)调用代码将无法按名称捕获它(如果你尝试,编译器将说:异常永远不会在相应的try语句的主体中抛出)。它会冒泡并可能​​在主程序循环中被一些"捕获异常"或"捕获Throwable"捕获,这可能是你想要的。

2)它违反了最少惊喜的原则:捕获RuntimeException以便能够保证捕获所有可能的异常将不再足够。出于这个原因,我认为这不应该在框架代码中完成,而只能在你完全控制的业务代码中完成。
总结:我认为这里的局限并不严重,可以毫不畏惧地使用UtilException类。但是,这取决于你! - 参考文献:http://www.philandstuff.com/2012/04/28/sneakily-throwing-checked-exceptions.html http://www.mail-archive.com/javaposse@googlegroups .com / msg05984.html Project Lombok注释:@SneakyThrows Brian Goetz在这里的意见(反对):如何从Java 8流中抛出CHECKED异常? https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e *