Java 8 方法引用未处理的异常

Posted

技术标签:

【中文标题】Java 8 方法引用未处理的异常【英文标题】:Java 8 method reference unhandled exception 【发布时间】:2014-10-27 21:31:23 【问题描述】:

我正在使用 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

IDEA 也给了我错误unhandled exception

那么,我的问题是为什么?为什么代码用for每个循环编译而不用lambda编译?

【问题讨论】:

顺便说一句,这不是一个 lambda 表达式 - 它是一个方法引用。如果您使用forEach(x -> deleteChild(x)),它将是一个 lambda 表达式。但同样的原因也会失败。 【参考方案1】:

如果您查看Consumer<T> 接口,accept 方法(这是您的方法引用将有效使用的)未声明为抛出任何检查异常 - 因此您不能使用方法引用声明抛出一个检查异常。增强的 for 循环没问题,因为您总是处于可以抛出 SomeException 的上下文中。

您可能会创建一个包装器,将已检查的异常转换为未检查的异常,然后抛出该异常。或者,您可以使用accept() 方法声明您自己的功能接口,该方法确实 抛出一个检查异常(可能使用该异常参数化接口),然后编写您自己的forEach 方法来接受它功能接口作为输入。

【讨论】:

您好,谢谢您的问题/谢谢您的回答。不使用 Java 8 及更高版本的已检查异常会怎样? 当然不是! :) 我一直在阅读关于人们不同意检查与检查的内容。未经检查的异常。请参阅example。这里的 Oracle 文档是关于如何使用受检异常的最终版本。然而,他们确实提到了限制检查异常强加于 lambda 的使用。我想知道这个限制是否足以避免使用受检异常。 @avi613:这更像是一个意见问题,真的...... 请举一些我自己的forEach的例子。我需要覆盖集合中的 forEach 吗? @KanagaveluSugumar:您不能为集合接口覆盖它,但您可以编写自己的静态方法接受集合。【参考方案2】:

你可以试试这个:

void deleteEntity(Node node) throws SomeException      node.getChildren().forEach(UtilException.rethrowConsumer(this::deleteChild));
    

下面的 UtilException 帮助类允许您在 Java 流中使用任何已检查的异常。请注意,上面的流还抛出了 this::deleteChild 抛出的原始检查异常,而不是一些包装的未检查异常。

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 语句的主体中永远不会抛出异常)。

• 在任何情况下,您都无法将流本身包围起来以在包含流的方法内部捕获已检查的异常 (如果你尝试,编译器会说:Exception is never throw in the body of对应的try语句)。

• 如果您调用的方法实际上永远不会抛出它声明的异常,那么您不应该包含 throws 子句。 例如:new String(byteArr, "UTF-8") 抛出 UnsupportedEncodingException,但 Java 规范保证 UTF-8 始终存在。 在这里, throws 声明很麻烦,欢迎任何使用最少样板来使其静音的解决方案。

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

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

• 在任何情况下,如果您决定不向包含流的方法的 throws 子句添加(或忘记添加)已检查异常, 请注意引发 CHECKED 异常的这两种后果:

1) 调用代码将无法通过名称捕获它(如果你尝试,编译器会说:Exception is never throw in the body of对应的try 陈述)。它会冒泡,并且可能在主程序循环中被一些“catch Exception”或“catch 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 意见(反对)在这里:How can I throw CHECKED exceptions from inside Java 8 streams? https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e *

【讨论】:

【参考方案3】:

请注意,尽管抛出异常,并行流仍将继续执行元素。

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class ThrowingConsumerTest 

    public static void main(String[] args) throws IOException 

        List<Integer> myIntegerList = new ArrayList<>();
        myIntegerList.add(1);
        myIntegerList.add(2);
        myIntegerList.add(3);
        myIntegerList.add(null);
        myIntegerList.add(4);
        myIntegerList.add(5);
        myIntegerList.add(6);
        myIntegerList.add(7);
        myIntegerList.add(8);
        myIntegerList.add(9);
        myIntegerList.add(10);
        myIntegerList.add(11);
        myIntegerList.add(12);
        myIntegerList.add(13);
        myIntegerList.add(14);
        myIntegerList.add(15);
        myIntegerList.add(16);
        myIntegerList.add(17);
        myIntegerList.add(18);
        myIntegerList.add(19);
        forEach(myIntegerList.stream(), ThrowingConsumerTest::exceptionThrowingConsumerCode);
    

    /**
     * Wrapper that converts Checked Exception to Runtime Exception
     */
    static <T, E extends Exception> Consumer<T> unchecked(ThrowingConsumer<T, E> consumer) 

        return (t) -> 
            try 
                consumer.accept(t);
             catch (Throwable e) 
                //Lambda can return only RuntimeException.
                RuntimeException ex = new RuntimeException();
                ex.addSuppressed(e);
                throw ex;
            
        ;
    

    /**
     * Wrapper that converts Runtime Exception to Checked Exception
     * Custom forEach; to accept the exception throwing consumer.
     */
    @SuppressWarnings("unchecked")
    static <T, E extends Exception> void forEach(Stream<T> s, ThrowingConsumer<T, E> consumer) throws E 

        try 

            s.parallel().forEach(unchecked(t -> consumer.accept(t)));
         catch (RuntimeException e) 
            //Checked Exception can be return from here
            throw (E) e.getSuppressed()[0];
        
    

    /*
     * Consumer that throws Exception
     */
    @FunctionalInterface
    public interface ThrowingConsumer<T, E extends Exception> 
        void accept(T t) throws E;
    

    static void exceptionThrowingConsumerCode(Object i) throws IOException 
        if (i == null) 
            throw new IOException();

         else 
            System.out.println(i);
        
    

【讨论】:

【参考方案4】:

您也可以声明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 块,但是一旦抛出异常,forEach 语句的执行将被中断。在上面的例子中,列表的"bar" 元素不会被打印出来。此外,这样做,您将无法跟踪 IDE 中引发的异常。

【讨论】:

【参考方案5】:

**如果您不想编写自己的消费者接口并使用它。您可以轻松使用自定义异常,如下所示。您可以执行如下操作。 **

list.stream().forEach(x->
try
System.out.println(x/0);
catch(ArithmeticException e)
throw new RuntimeException(new MyCustomException(FirstArgument,SecondArgument));
);

【讨论】:

【参考方案6】:

查看图书馆Throwing Function:

通过应用 com.pivovarit.function 函数式接口,可以重新获得清晰和可读性:

ThrowingFunction<String, URI, URISyntaxException> toUri = URI::new;

并通过使用自定义 ThrowingFunction#unchecked 适配器将它们与本机 java.util.function 类无缝使用:

...stream()
  .map(unchecked(URI::new)) // static import of ThrowingFunction#unchecked
  .forEach(System.out::println);

【讨论】:

以上是关于Java 8 方法引用未处理的异常的主要内容,如果未能解决你的问题,请参考以下文章

JNI官方中文资料

AsQueryable() 返回用户未处理的 NullReference 异常“对象引用未设置为对象实例”

未将对象引用设置到对象的实例

Java 8 新特性-菜鸟教程 -Java 8 方法引用

Java 8新特性-4 方法引用

Roles.GetRolesForUser 抛出异常对象引用未设置为对象的实例