Java检查的异常的解决方法


49

我非常感谢有关lambda和默认方法接口的Java 8新功能。但是,我仍然对检查的异常感到无聊。例如,如果我只想列出对象的所有可见字段,我想简单地写一下:

    Arrays.asList(p.getClass().getFields()).forEach(
        f -> System.out.println(f.get(p))
    );

但是,由于该get方法可能会抛出一个与Consumer接口协定不同的已检查异常,所以我必须捕获该异常并编写以下代码:

    Arrays.asList(p.getClass().getFields()).forEach(
            f -> {
                try {
                    System.out.println(f.get(p));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
            }
    );

但是,在大多数情况下,我只希望将异常作为a抛出,RuntimeException并让程序处理或不处理没有编译错误的异常。

因此,我想就我对有争议的异常进行检查的方法发表您的看法。为此,我创建了一个辅助接口ConsumerCheckException<T>和一个实用程序功能rethrow根据Doval的评论进行了更新),如下所示:

  @FunctionalInterface
  public interface ConsumerCheckException<T>{
      void accept(T elem) throws Exception;
  }

  public class Wrappers {
      public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
        return elem -> {
          try {
            c.accept(elem);
          } catch (Exception ex) {
            /**
             * within sneakyThrow() we cast to the parameterized type T. 
             * In this case that type is RuntimeException. 
             * At runtime, however, the generic types have been erased, so 
             * that there is no T type anymore to cast to, so the cast
             * disappears.
             */
            Wrappers.<RuntimeException>sneakyThrow(ex);
          }
        };
      }

      /**
       * Reinier Zwitserloot who, as far as I know, had the first mention of this
       * technique in 2009 on the java posse mailing list.
       * http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
       */
      public static <T extends Throwable> T sneakyThrow(Throwable t) {
          throw (T) t;
      }
  }

现在我可以写:

    Arrays.asList(p.getClass().getFields()).forEach(
            rethrow(f -> System.out.println(f.get(p)))
    );

我不确定这是否是克服检查异常的最佳方法,但是正如我所解释的,我希望有一种更方便的方式来实现我的第一个示例,而无需处理检查异常,这是我发现的更简单的方法去做吧。



2
除了Robert的链接之外,还请查看Sneakily Throwing Checked Exceptions。如果需要,您可以sneakyThrow在内部使用rethrow引发经过检查的原始异常,而不必将其包装在中RuntimeException。或者,您可以使用Project Lombok中的@SneakyThrows注释执行相同的操作。
2014年

1
还要注意,使用并行s 时,可以并行执行Consumers in 。与消费者一起引发的可抛出异常将传播到调用线程,该线程1)不会停止其他同时运行的消费者,这可能是适当的,也可能是不合适的; 2)如果一个以上的消费者抛出了某些东西,则仅调用线程将看到其中之一。forEachStream
乔纳斯·普拉卡


2
Lambda非常丑陋。
图兰斯·科尔多瓦

Answers:


16

技术的优点,缺点和局限性:

  • 如果调用代码是要处理检查后的异常,则必须将其添加到包含流的方法的throws子句中。编译器不会再强迫您添加它,因此更容易忘记它。例如:

    public void test(Object p) throws IllegalAccessException {
        Arrays.asList(p.getClass().getFields()).forEach(rethrow(f -> System.out.println(f.get(p))));
    }
    
  • 如果调用代码已经处理了检查的异常,则编译器将提醒您将throws子句添加到包含流的方法声明中(如果不这样做,它将说:永远不会在相应的try语句的主体中引发异常) 。

  • 无论如何,您将无法包围流本身以捕获包含流的方法在内的已检查异常(如果尝试,编译器会说:永远不会在相应的try语句的主体中引发异常)。

  • 如果要调用的方法从字面上讲永远不会抛出它所声明的异常,则不应包含throws子句。例如:new String(byteArr,“ UTF-8”)引发UnsupportedEncodingException,但是Java规范保证UTF-8始终存在。在这里,throws声明是很麻烦的,欢迎任何以最小的样板将其静音的解决方案。

  • 如果您讨厌检查异常,并且觉得绝不应该将它们添加到Java语言中(越来越多的人以这种方式,但我不是其中之一),那么就不要在抛出异常时添加检查异常包含流的方法的子句。然后,已检查的异常的行为就像未检查的异常。

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

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

    1. 调用代码将无法按名称捕获它(如果尝试,编译器会说:永远不会在相应的try语句的主体中引发异常)。它会冒泡,并且可能在主程序循环中被某些“ catch Exception”或“ catch Throwable”捕获,无论如何,这可能是您想要的。

    2. 它违反了最少惊讶的原则:它不再足以捕获RuntimeException来保证捕获所有可能的异常。因此,我认为这不应在框架代码中完成,而应在完全控制的业务代码中完成。

参考文献:

注意:如果您决定使用此技术,则可以LambdaExceptionUtil从StackOverflow 复制帮助器类:https ://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 -流。它通过示例为您提供了完整的实现(功能,消费者,供应商...)。


6

在这个例子中,它真的会失败吗?别这么认为,但也许您的情况很特殊。如果它真的“不能失败”,并且它只是一个令人讨厌的编译器,那么我喜欢包装该异常并Error在注释中添加“不能发生”。使维护变得非常清楚。否则,他们会怀疑“这怎么会发生”和“到底是谁来处理的?”

这在YMMV中引起争议。我可能会得到一些赞成票。


3
+1,因为您会抛出一个错误。经过数小时的调试,在仅包含一行注释的catch块中“经过了几次”之后,我最终经历了多少次……
Axel 2014年

3
-1,因为您将引发错误。错误是指JVM中有问题,上级可以相应地处理它们。如果选择这种方式,则应抛出RuntimeException。另一个可能的解决方法是断言(需要启用-ea标志)或日志记录。
duros 2014年

3
@duros:取决于异常“为什么不会发生”的原因,引发异常的事实可能表明某件事是严重错误的。例如,假设某人调用cloneFoo已知支持它的密封类型,然后抛出CloneNotSupportedException。除非代码与其他意外类型链接在一起,怎么会这样Foo?如果发生这种情况,可以信任任何东西吗?
2014年

1
@supercat很好的例子。其他人则因缺少字符串编码或MessageDigests而引发异常。如果缺少UTF-8或SHA,则您的运行时可能已损坏。
user949300 2014年

1
@duros这是一个完全的误解。An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.(引自Javadoc的Error)这正是这种情况,因此,远非不足,在Error此处抛出一个是最合适的选择。
biziclop 2015年

1

此代码的另一个版本,其中已延迟检查异常:

public class Cocoon {
         static <T extends Throwable> T forgetThrowsClause(Throwable t) throws T{
            throw (T) t;
        }

        public static <X, T extends Throwable> Consumer<X> consumer(PeskyConsumer<X,T> touchyConsumer) throws T {
            return new Consumer<X>() {
                @Override
                public void accept(X t) {
                    try {
                        touchyConsumer.accept(t);
                    } catch (Throwable exc) {
                        Cocoon.<RuntimeException>forgetThrowsClause(exc) ;
                    }

                }
            } ;
        }
// and so on for Function, and other codes from java.util.function
}

魔术是,如果您致电:

 myArrayList.forEach(Cocoon.consumer(MyClass::methodThatThrowsException)) ;

那么您的代码将不得不捕获异常


如果这是在并行流上运行的,那么元素在不同的线程中使用会发生什么情况?
修剪

0

偷偷摸摸扔!我完全感到你的痛苦。

如果您必须留在Java中...

Paguro具有包装受检查的异常的功能接口,因此您无需再考虑这一点。它包括Clojure转换器(或Java 8 Streams)沿线的不可变集合和功能转换,这些转换器采用功能接口并包装检查的异常。

还可以尝试使用VAVrEclipse Collections

否则,请使用Kotlin

Kotlin与Java 2向兼容,没有经过检查的异常,没有原语(嗯,程序员不必考虑),一个更好的类型系统,具有不变性,等等。即使我在上面策划了Paguro库,我仍然我将所有可以转换的Java代码转换为Kotlin。我以前在Java中所做的任何事情现在都更喜欢在Kotlin中进行。


有人对此进行了否决,这很好,但是除非您告诉我为什么您对此不赞成,否则我不会学到任何东西。
GlenPeterson '16

1
在github中为您的库+1,您还可以检查eclipse.org/collections或project vavr,它们提供了功能齐全且不可变的集合。您对此有何看法?
firephil

-1

受检查的异常非常有用,它们的处理方式取决于您编写代码的产品类型:

  • 图书馆
  • 桌面应用程序
  • 在某个盒子内运行的服务器
  • 学术练习

通常,库不得处理已检查的异常,而应声明为由公共API抛出。例如,将它们包装到简单的RuntimeExcepton中的罕见情况是,在库代码内部创建一些私有xml文档并进行解析,然后创建一些临时文件,然后读取该文件。

对于桌面应用程序,必须通过创建自己的异常处理框架来开始产品开发。通常,使用您自己的框架,您可以将检查的异常包装到两到四个包装器(您的RuntimeExcepion的自定义子类)中,并由一个异常处理程序处理它们。

对于服务器,通常您的代码将在某些框架中运行。如果您不使用任何服务器(裸服务器),请创建您自己的类似上述框架(基于运行时)。

对于学术练习,您始终可以将它们直接包装到RuntimeExcepton中。有人也可以创建一个很小的框架。


-1

您可能想看看这个项目;我专门创建它是因为检查异常和功能接口的问题。

使用它,您可以执行此操作,而不用编写您的自定义代码:

// I don't know the type of f, so it's Foo...
final ThrowingConsumer<Foo> consumer = f -> System.out.println(f.get(p));

由于所有这些Throwing *接口都扩展了它们的非Throwing对应对象,因此您可以直接在流中使用它们:

Arrays.asList(p.getClass().getFields()).forEach(consumer);

或者,您可以:

import static com.github.fge.lambdas.consumers.Consumers.wrap;

Arrays.asList(p.getClass().getFields())
    .forEach(wrap(f -> System.out.println(f.get(p)));

和其他东西。


更好的是fields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))
user2418306 '16

请拒绝者解释一下?
fge
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.