如何从 Java 8 流/lambda 中抛出 CHECKED 异常?
换句话说,我想让这样的代码编译:
public List<Class> getClasses() throws ClassNotFoundException { List<Class> classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String") .map(className -> Class.forName(className)) .collect(Collectors.toList()); return classes; }
此代码无法编译,因为Class.forName()上面的方法 throwsClassNotFoundException已被检查。
Class.forName()
ClassNotFoundException
请注意,我不想将检查的异常包装在运行时异常中,而是抛出包装的未经检查的异常。 我想抛出检查的异常本身 ,而不是在流中添加丑陋的try/ 。catches
try
catches
这个LambdaExceptionUtil帮助类允许您在 Java 流中使用任何已检查的异常,如下所示:
LambdaExceptionUtil
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String") .map(rethrowFunction(Class::forName)) .collect(Collectors.toList());
注意Class::forNamethrows ClassNotFoundException,这是 选中 的。流本身也会抛出ClassNotFoundException,而不是一些包装未经检查的异常。
Class::forName
public final class LambdaExceptionUtil { @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; } }
关于如何使用它的许多其他示例(在静态导入之后LambdaExceptionUtil):
@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"); }
截至 2015 年 11 月 的更新代码在@PaoloC 的帮助下得到了改进,请在下面查看他的答案。他帮助解决了最后一个问题:现在编译器会要求您添加throw 子句,一切就好像您可以在 Java 8 流上本地抛出已检查异常一样。
你可以!
扩展 @marcgUtilException并throw E在必要时添加:这样,编译器会要求您添加 throw 子句,一切就好像您可以在 java 8 的流上本地抛出已检查的异常一样。
UtilException
throw E
说明:只需复制/粘贴LambdaExceptionUtil到您的 IDE 中,然后使用它,如下所示LambdaExceptionUtilTest。
LambdaExceptionUtilTest
public final class LambdaExceptionUtil { @FunctionalInterface public interface Consumer_WithExceptions<T, E extends Exception> { void accept(T t) throws E; } @FunctionalInterface public interface Function_WithExceptions<T, R, E extends Exception> { R apply(T t) throws E; } /** * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); */ 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) { throwActualException(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) { throwActualException(exception); return null; } }; } @SuppressWarnings("unchecked") private static <E extends Exception> void throwActualException(Exception exception) throws E { throw (E) exception; } }
一些测试以显示用法和行为:
public class LambdaExceptionUtilTest { @Test(expected = MyTestException.class) public void testConsumer() throws MyTestException { Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s))); } private void checkValue(String value) throws MyTestException { if(value==null) { throw new MyTestException(); } } private class MyTestException extends Exception { } @Test public void testConsumerRaisingExceptionInTheMiddle() { MyLongAccumulator accumulator = new MyLongAccumulator(); try { Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s))); fail(); } catch (MyTestException e) { assertEquals(9L, accumulator.acc); } } private class MyLongAccumulator { private long acc = 0; public void add(Long value) throws MyTestException { if(value==null) { throw new MyTestException(); } acc += value; } } @Test public void testFunction() throws MyTestException { List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList()); assertEquals(2, sizes.size()); assertEquals(4, sizes.get(0).intValue()); assertEquals(5, sizes.get(1).intValue()); } private Integer transform(String value) throws MyTestException { if(value==null) { throw new MyTestException(); } return value.length(); } @Test(expected = MyTestException.class) public void testFunctionRaisingException() throws MyTestException { Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList()); } }
注 1 上述类的rethrow方法LambdaExceptionUtil可以毫无顾忌地使用,并且可以 在任何情况下使用 。
rethrow
注意 2: 上述类的uncheck方法LambdaExceptionUtil是额外的方法,如果您不想使用它们,可以安全地将它们从类中删除。如果您确实使用过它们,请谨慎使用,不要在了解以下用例、优点/缺点和限制之前:
uncheck
uncheck——如果你调用一个从字面上永远不会抛出它声明的异常的方法,你可以使用这些方法。例如:new String(byteArr, “UTF-8”) 抛出 UnsupportedEncodingException,但 Java 规范保证 UTF-8 始终存在。在这里, throws 声明很麻烦,欢迎使用任何用最少的样板来使其静音的解决方案:String text = uncheck(() -> new String(byteArr, "UTF-8"));
String text = uncheck(() -> new String(byteArr, "UTF-8"));
——如果你正在实现一个严格的接口,你可以使用这些uncheck方法,而你没有添加 throws 声明的选项,但抛出异常是完全合适的。包装一个异常只是为了获得抛出它的特权会导致一个带有虚假异常的堆栈跟踪,这些异常不会提供有关实际错误的信息。一个很好的例子是 Runnable.run(),它不会抛出任何已检查的异常。
’无论如何,如果您决定使用这些uncheck方法,请注意在没有 throws 子句的情况下抛出 CHECKED 异常的这两个后果:1) 调用代码将无法通过名称捕获它(如果您尝试,编译器会说:在相应的 try 语句的主体中永远不会抛出异常)。它会冒泡,并且可能会被一些“catch Exception”或“catch Throwable”捕获在主程序循环中,这可能是你想要的。2)它违反了最小意外原则:它不再足以捕获RuntimeException能够保证捕获所有可能的异常。出于这个原因,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成。
RuntimeException