Java 8:Lambdastream,按exception进行过滤
我尝试使用Java 8的Lambdaexpression式时遇到问题。通常情况下,它可以正常工作,但是现在我有了抛出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的可能exception。 但即使我明确地使用下面的try-catch-Block,它仍然不能编译,因为我没有捕捉到exception。 所以无论是在JDK中有一个错误,或者我不知道如何捕获这些exception。
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){ } } .... }
我怎样才能得到它的工作? 有人可以提示我正确的解决scheme吗?
在转义lambda 之前 ,您必须捕获exception:
s = s.filter(a -> { try { return a.isActive(); } catch (IOException e) { throw new UncheckedIOException(e); }}});
考虑一下这样一个事实,lambda不是在你写的地方进行评估,而是在一个完全不相关的地方,在一个JDK类中。 所以这将是检查exception将被抛出的地方,并在那个地方没有宣布。
是的,检查exception一直是一个痛苦,现在与性感和简洁的lambda语法,他们扰乱程序stream更糟糕。
在我的项目中,我处理这个问题,没有包装; 相反,我使用了一种有效地消除编译器检查exception的方法。 不用说,这应该小心处理,项目中的每个人都必须意识到,检查的exception可能出现在未声明的地方。 这是pipe道代码:
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; } public static <T> T sneakyThrow(Throwable e) { return Util.<RuntimeException, T>sneakyThrow0(e); } private static <E extends Throwable, T> T sneakyThrow0(Throwable t) throws E { throw (E)t; }
你的例子会写成
return s.filter(a -> uncheckCall(a::isActive)) .map(Account::getNumber) .collect(toSet());
你可以期望得到一个IOException
抛出你的脸,尽pipecollect
不会声明它。 在大多数情况下,但并非所有真实情况下,您都希望重新抛出exception,并将其作为通用失败处理。 在所有这些情况下,没有什么是明确或正确的。 只要小心其他情况下,你真的想在现场对exception做出反应。 开发人员不会意识到编译器有一个IOException
在那里捕捉,而编译器实际上会抱怨,如果你试图捕捉它,因为我们已经愚弄它,相信没有这样的exception可以抛出。
你也可以传播你的静态痛苦与lambda,所以整个事情看起来是可读的:
s.filter(a -> propagate(a::isActive))
在这里propagate
接收java.util.concurrent.Callable
作为参数,并将在调用期间捕获的任何exception转换为RuntimeException
。 在Guava中有一个类似的转换方法Throwables#propagate(Throwable) 。
这个方法对于lambda方法链接来说似乎是必不可less的,所以我希望有一天它会被添加到一个stream行的库中,或者这个传播行为是默认的。
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); } }
这个UtilException
助手类允许你在Javastream中使用任何检查的exception,如下所示:
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String") .map(rethrowFunction(Class::forName)) .collect(Collectors.toList());
注意Class::forName
抛出ClassNotFoundException
,它被选中 。 stream本身也抛出ClassNotFoundException
,而不是一些包装uncheckedexception。
public final class UtilException { @FunctionalInterface public interface Consumer_WithExceptions<T> { void accept(T t) throws Exception; } @FunctionalInterface public interface Function_WithExceptions<T, R> { R apply(T t) throws Exception; } @FunctionalInterface public interface Supplier_WithExceptions<T> { T get() throws Exception; } @FunctionalInterface public interface Runnable_WithExceptions { void accept() throws Exception; } /** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */ public static <T> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T> consumer) { return t -> { try { consumer.accept(t); } catch (Exception exception) { throwAsUnchecked(exception); } }; } /** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */ public static <T, R> Function<T, R> rethrowFunction(Function_WithExceptions<T, R> function) { 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> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T> function) { 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.accept(); } catch (Exception exception) { throwAsUnchecked(exception); } } /** uncheck(() -> Class.forName("xxx")); */ public static <R> R uncheck(Supplier_WithExceptions<R> supplier) { try { return supplier.get(); } catch (Exception exception) { throwAsUnchecked(exception); return null; } } /** uncheck(Class::forName, "xxx"); */ public static <T, R> R uncheck(Function_WithExceptions<T, R> 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"); }
但在理解以下优点,缺点和限制之前,请不要使用它 :
•如果调用代码处理检查的exception,则必须将其添加到包含该stream的方法的throws子句中。 编译器不会强迫你再添加它,所以忘记它更容易。
•如果调用代码已经处理了检查的exception,编译器会提醒您将throws子句添加到包含该stream的方法声明中(如果不这样做):Exception永远不会在相应的try语句的主体中抛出)。
•在任何情况下,您都不能围绕stream本身来捕获包含stream的方法中的检查exception(如果您尝试,编译器会说:Exception永远不会在相应的try语句的主体中抛出)。
•如果您正在调用一个方法,它永远不会抛出它声明的exception,那么您不应该包含throws子句。 例如:new String(byteArr,“UTF-8”)会抛出UnsupportedEncodingException,但是Java规范保证UTF-8始终存在。 在这里,抛出声明是一个麻烦,任何解决scheme,以最小的样板沉默它是受欢迎的。
•如果您讨厌检查exception,并认为不应该将它们添加到Java语言中(以越来越多的人这样认为,而我不是其中之一),那么就不要将检查的exception添加到抛出包含该stream的方法的子句。 然后,检查的exception就像UNcheckedexception一样。
•如果您正在实现一个严格的接口,而您没有添加throws声明的选项,而抛出一个exception是完全合适的,那么只是为了获得抛出exception的特权而包装一个exception会导致带有虚假exception的堆栈跟踪没有提供有关实际发生错误的信息。 一个很好的例子是Runnable.run(),它不会抛出任何检查的exception。 在这种情况下,您可能决定不将检查的exception添加到包含该stream的方法的throws子句中。
•在任何情况下,如果您决定不向包含该stream的方法的throws子句添加(或忘记添加)检查的exception,请注意引发CHECKEDexception的这两个后果:
1)调用代码将无法通过名称来捕获它(如果您尝试,编译器会说:Exception从不会在相应的try语句的主体中抛出)。 它会冒泡,并可能在主程序循环中被一些“catch Exception”或“catch Throwable”捕获,这可能是你想要的。
2)它违反了最less惊喜的原则:捕获RuntimeException将不能保证捕获所有可能的exception。 出于这个原因,我认为这不应该在框架代码中完成,而只能在完全控制的业务代码中完成。
总之:我认为这里的局限性并不严重, UtilException
类可以毫不畏惧地使用。 但是,这取决于你!
- 参考文献:
- http://www.philandstuff.com/2012/04/28/sneakily-throwing-checked-exceptions.html
- http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
- 项目龙目岛注释:@SneakyThrows
- Brian Goetz的意见(反对)在这里: 我怎样才能从Java 8stream内引发CHECKEDexception?
- https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e *
你可以通过包装你的lambda来抛出一个未经检查的exception,然后在terminal操作中解开这个未经检查的exception,
@FunctionalInterface public interface ThrowingPredicate<T, X extends Throwable> { public boolean test(T t) throws X; } @FunctionalInterface public interface ThrowingFunction<T, R, X extends Throwable> { public R apply(T t) throws X; } @FunctionalInterface public interface ThrowingSupplier<R, X extends Throwable> { public R get() throws X; } public interface ThrowingStream<T, X extends Throwable> { public ThrowingStream<T, X> filter( ThrowingPredicate<? super T, ? extends X> predicate); public <R> ThrowingStream<T, R> map( ThrowingFunction<? super T, ? extends R, ? extends X> mapper); public <A, R> R collect(Collector<? super T, A, R> collector) throws X; // etc } class StreamAdapter<T, X extends Throwable> implements ThrowingStream<T, X> { private static class AdapterException extends RuntimeException { public AdapterException(Throwable cause) { super(cause); } } private final Stream<T> delegate; private final Class<X> x; StreamAdapter(Stream<T> delegate, Class<X> x) { this.delegate = delegate; this.x = x; } private <R> R maskException(ThrowingSupplier<R, X> method) { try { return method.get(); } catch (Throwable t) { if (x.isInstance(t)) { throw new AdapterException(t); } else { throw t; } } } @Override public ThrowingStream<T, X> filter(ThrowingPredicate<T, X> predicate) { return new StreamAdapter<>( delegate.filter(t -> maskException(() -> predicate.test(t))), x); } @Override public <R> ThrowingStream<R, X> map(ThrowingFunction<T, R, X> mapper) { return new StreamAdapter<>( delegate.map(t -> maskException(() -> mapper.apply(t))), x); } private <R> R unmaskException(Supplier<R> method) throws X { try { return method.get(); } catch (AdapterException e) { throw x.cast(e.getCause()); } } @Override public <A, R> R collect(Collector<T, A, R> collector) throws X { return unmaskException(() -> delegate.collect(collector)); } }
那么你可以像Stream
一样使用它:
Stream<Account> s = accounts.values().stream(); ThrowingStream<Account, IOException> ts = new StreamAdapter<>(s, IOException.class); return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet());
这个解决scheme将需要相当多的样板,所以我build议你看看我已经制作的库 ,它完全是我在这里描述的整个Stream
类(和更多!)。
使用#propagate()方法。 来自Java 8 Blog的非Guava实现示例: Sam Beran :
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); } } }
它可以解决以下简单的代码与stream和尝试在AbacusUtil :
Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet();
披露:我是AbacusUtil
的开发者。
扩展@marcg解决scheme,您通常可以在Streams中抛出并捕获检查的exception; 也就是说, 编译器会要求你像在外面stream一样抓/重新扔 !
@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; }
然后,你的例子会写成如下(添加testing,以更清楚地显示):
@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 } }
要正确添加处理代码的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
exception,所以不需要额外的代码:
Set<String> set = new HashSet<>(); for(Account a: accounts.values()){ if(a.isActive()){ set.add(a.getNumber()); } } return set;
记住这个问题,我开发了一个小型库来处理检查的exception和lambdaexpression式。 自定义适配器允许您与现有functiontypes进行集成:
stream().map(unchecked(URI::new)) //with a static import
你的例子可以写成:
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
如果你不介意使用第三方库,美国在线的独眼巨人反应 lib,披露::我是一个贡献者,有一个ExceptionSoftener类,可以在这里帮助。
s.filter(softenPredicate(a->a.isActive()));
这并不直接回答这个问题(还有很多其他的答案),但是首先要避免这个问题:
根据我的经验,处理Stream
(或其他lambdaexpression式)中的exception的需求常常来自这样一个事实:exception被声明为不应该抛出的方法抛出。 这通常来自混合业务逻辑与input和输出。 您的Account
界面就是一个完美的例子:
interface Account { boolean isActive() throws IOException; String getNumber() throws IOException; }
不要在每个getter上抛出一个IOException
,而要考虑这个devise:
interface AccountReader { Account getAccount(…) throws IOException; } interface Account { boolean isActive(); String getNumber(); }
AccountReader.getAccount(…)
方法可以从数据库或文件或其他任何地方读取帐户,如果不成功则抛出exception。 它构造一个已经包含所有值的Account
对象,可以使用。 由于值已经被getAccount(…)
加载,getter不会抛出exception。 因此,您可以在lambdas中自由使用它们,而不需要包装,掩盖或隐藏exception。
当然,按照我所描述的方式来做并不总是可能的,但是通常情况下它会导致更干净的代码(恕我直言):
- 更好的分离问题 ,遵循单一的责任原则
- 更less的样板:你不必乱
throws IOException
代码throws IOException
没有用,但为了满足编译器 - error handling:当你从一个文件或数据库中读取数据时,你处理错误的地方 – 而不是你的业务逻辑中的某个地方,因为你想获得一个字段值
- 您可以使
Account
不变,并从中获益(例如线程安全) - 你不需要“肮脏的窍门”或解决方法来使用
Account
lambda(例如在一个Stream
)