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

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


当前回答

它可以通过以下简单的代码在abacus-common中使用Stream和Try来解决:

Stream.of(accounts)
      .filter(a -> Try.call(a::isActive))
      .map(a -> Try.call(a::getNumber))
      .toSet();

Or:

Stream.of(accounts)
      .filterE(Account::isActive)
      .mapE(Account::getNumber)
      .toSet();

披露:我是abacus-common的开发者。

其他回答

你的例子可以写成:

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

TLDR:尝试通过重构代码来避免这个问题:将“容易出错”的操作与“安全”的操作分开,只使用lambdas中的安全操作。


细节:

这并没有直接回答问题(有很多其他的答案),但试图从一开始就避免这个问题:

根据我的经验,在Stream(或其他lambda表达式)中处理异常的需求通常来自这样一个事实,即异常被声明为从不应该抛出的方法抛出。这通常来自于将业务逻辑与输入和输出混合。你的帐户界面就是一个完美的例子:

interface Account {
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
}

不要在每个getter上抛出IOException,考虑这样的设计:

interface AccountReader {
    Account readAccount(…) throws IOException;
}

interface Account {
    boolean isActive();
    String getNumber();
}

方法AccountReader.readAccount(…)可以从数据库或文件中读取帐户,如果未成功则抛出异常。它构造一个Account对象,该对象已经包含所有值,可以随时使用。由于这些值已经被readAccount(…)加载,getter不会抛出异常。因此,你可以在lambdas中自由地使用它们,而不需要包装、屏蔽或隐藏异常。

注意,您仍然需要处理readAccount(…)抛出的异常。毕竟,这就是异常存在的首要原因。但是假设readAccount(…)是在“其他地方”使用的,即在lambdas之外,在那里你可以使用Java提供的“正常”异常处理机制,即try-catch来处理它或throws来让它“冒泡”。

当然,不可能总是按照我描述的方式来做,但通常是这样的,它会导致更干净的代码(恕我直言):

Better separation of concerns and following single responsibility principle Less boilerplate: You don't have to clutter your code with throws IOException for no use but to satisfy the compiler Error handling: You handle the errors where they happen - when reading from a file or database - instead of somewhere in the middle of your business logic only because you want to get a fields value You may be able to make Account immutable and profit from the advantages thereof (e.g. thread safety) You don't need "dirty tricks" or workarounds to use Account in lambdas (e.g. in a Stream)

考虑到这个问题,我开发了一个小型库来处理受控异常和lambdas。自定义适配器允许您与现有的函数类型集成:

stream().map(unchecked(URI::new)) //with a static import

https://github.com/TouK/ThrowingFunction/

这个UtilException helper类允许你在Java流中使用任何受控异常,如下所示:

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

注意:Class::forName抛出ClassNotFoundException,该异常被检查。流本身也会抛出ClassNotFoundException和NOT一些未检查的包装异常。

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语句体中抛出)。

•在任何情况下,你都不能环绕流本身来捕获包含流的方法内部的检查异常 (如果你尝试,编译器会说:异常永远不会在相应的try语句体中抛出)。

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

•如果你讨厌受控异常,觉得它们从一开始就不应该被添加到Java语言中(越来越多的人这样认为, 并且我不是其中之一),那么就不要将检查异常添加到包含流的方法的throws子句中。的检查 异常的行为就像一个未检查的异常。

• If you are implementing a strict interface where you don't have the option for adding a throws declaration, and yet throwing an exception is entirely appropriate, then wrapping an exception just to gain the privilege of throwing it results in a stacktrace with spurious exceptions which contribute no information about what actually went wrong. A good example is Runnable.run(), which does not throw any checked exceptions. In this case, you may decide not to add the checked exception to the throws clause of the method that contains the stream.

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

1)调用代码将无法通过名称捕获它(如果你尝试,编译器会说:异常永远不会在相应的try体中抛出 声明)。它会冒泡,并可能在主程序循环中被一些“catch Exception”或“catch Throwable”捕获,这可能是你 希望无论如何。

2)它违反了最小意外原则:仅仅捕获RuntimeException就不足以保证捕获所有的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 *

扩展@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
    }
}