Java 8方法引用未处理的exception
我正在使用Java 8开发项目,发现一个我无法理解的情况。
我有这样的代码:
void deleteEntity(Node node) throws SomeException { for (ChildNode child: node.getChildren()) { deleteChild(child); } } void deleteChild(Object child) throws SomeException { //some code }
此代码工作正常,但我可以用方法引用重写它:
void deleteEntity(Node node) throws SomeException { node.getChildren().forEach(this::deleteChild); }
而且这段代码不能编译, Incompatible thrown types *SomeException* in method reference
给出错误Incompatible thrown types *SomeException* in method reference
。
还IDEA给了我错误unhandled exception
。
那么,我的问题是为什么? 为什么代码为每个循环编译,不用lambda编译?
如果您查看Consumer<T>
接口,那么accept
方法(这是您的方法引用将实际使用的)不会被声明为抛出任何检查的exception – 因此您不能使用声明为抛出的方法引用一个检查的exception。 增强的for循环是可以的,因为在那里你总是可以抛出SomeException
的上下文。
您可能会创build一个包装器,将检查的exception转换为未经检查的exception,然后抛出exception。 或者,你可以用一个accept()
方法来声明你自己的函数接口,这个方法会抛出一个检查的exception(可能用这个exception对接口进行参数化),然后编写你自己的forEach
方法,把这个函数接口作为input。
你可以试试这个:
void deleteEntity(Node node) throws SomeException { node.getChildren().forEach(UtilException.rethrowConsumer(this::deleteChild)); }
下面的UtilException
帮助器类允许您使用Javastream中的任何已检查的exception。 注意上面的stream也抛出了this::deleteChild
引发的原始检查exception,而不是一些包装未经检查的exception。
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 *
您也可以声明someException
以便扩展RuntimeException
而不是Exception
。 下面的代码将被编译:
public class Test { public static void main(String[] args){ // TODO Auto-generated method stub List<String> test = new ArrayList<String>(); test.add("foo"); test.add(null); test.add("bar"); test.forEach(x -> print(x)); } public static class SomeException extends RuntimeException{ } public static void print(String s) throws SomeException{ if (s==null) throw new SomeException(); System.out.println(s); } }
输出将是:
foo Exception in thread "main" simpleTextLayout.Test$SomeException at simpleTextLayout.Test.print(Test.java:22) at simpleTextLayout.Test.lambda$0(Test.java:14) at java.util.ArrayList.forEach(ArrayList.java:1249) at simpleTextLayout.Test.main(Test.java:14)
您可以在forEach
语句中添加一个try/catch
块,但是一旦抛出exception, forEach
语句的执行就会中断。 在上面的例子中,列表的"bar"
元素将不会被打印。 而且,通过这样做,您将会丢失在IDE中抛出的exception。