【问题标题】:When catch doesn't actually catch anything [duplicate]当 catch 实际上没有捕获任何东西时[重复]
【发布时间】:2018-10-26 23:31:56
【问题描述】:

由于最近在数据库中存储了错误数据,我遇到了程序崩溃。这让我很困惑,因为我认为我有办法防止这种情况发生。

以下代码的目的是比较员工徽章编号并对其进行排序。如果出现错误,则返回 -1 并继续前进——不要因为数千个徽章编号中的一个错误而停止:

public int compare(Employee t, Employee t1) {
    Integer returnValue = -1;
    try {
        Integer tb = Integer.parseInt(t.getBadgeNumber());
        Integer t1b = Integer.parseInt(t1.getBadgeNumber());
        returnValue = tb.compareTo(t1b);
    } catch (Exception e) {
        returnValue = -1;//useless statement, I know.
    }
    return returnValue;
}

当错误的徽章编号命中时(在本例中为 t),我收到“java.lang.IllegalArgumentException:比较方法违反其一般合同!”错误而不是在 catch 中返回 -1。

我对这里的捕获有什么不明白的地方?

完整的堆栈跟踪:

16-May-2018 14:28:53.496 SEVERE [http-nio-8084-exec-601] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [RequestServlet] in context with path [/AppearanceRequest] threw exception
 java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:868)
at java.util.TimSort.mergeAt(TimSort.java:485)
at java.util.TimSort.mergeForceCollapse(TimSort.java:426)
at java.util.TimSort.sort(TimSort.java:223)
at java.util.TimSort.sort(TimSort.java:173)
at java.util.Arrays.sort(Arrays.java:659)
at java.util.Collections.sort(Collections.java:217)
at org.bcso.com.appearancerequest.html.NotifierHTML.getHTML(NotifierHTML.java:363)
at org.bcso.com.appearancerequest.AppearanceRequestServlet.processRequest(AppearanceRequestServlet.java:96)
at org.bcso.com.appearancerequest.AppearanceRequestServlet.doGet(AppearanceRequestServlet.java:565)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:74)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1015)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:652)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1575)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1533)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)

调用代码:

    List<Employee> employeeList = DatabaseUtil.getEmployees();
    Collections.sort(employeeList, new BadgeComparator());

【问题讨论】:

  • IllegalArgumentException 确实如此。
  • 请发布完整的堆栈跟踪。你的异常应该被那个 try-catch 捕获。所以我认为例外来自其他地方。请发布minimal reproducible example(完整)。
  • 异常不是来自您的比较方法。它是由检测到您的 compareTo 实现错误的 sorter 方法引发的。您需要按照其文档中的说明来实现它。也就是说,如果第一个小于第二个,则为负,如果第一个大于第二个,则为正,如果相等,则为0
  • 故事的寓意:不要盲目地捕获您无法处理的异常并且仍然​​可以正常运行。如果您必须处理非整数BadgeNumbers,则它们必须在您的排序中有适当的位置(即大于所有数字或小于所有数字)
  • 使用minimal reproducible example 这个问题会更有用。就目前而言,它是基于对抛出异常的位置的误解,这使得它不太可能对其他人有所帮助。考虑一下您可以使用 if 语句而不是 try-catch 获得相同的异常,这对于您为什么会收到异常以及如何修复它有完全相同的答案。此外,这种误解意味着答案需要解决这个问题,而不是更笼统地解决类似但不相同的问题。听起来很值得 - 不知道为什么它被重新打开了。

标签: java exception try-catch comparator


【解决方案1】:

异常(无论是什么)catch (Exception e) 捕获。你没有记录这个异常,所以你不知道它是什么。你应该以某种方式记录它,这样你就知道到底发生了什么。

当您返回-1 时会出现问题。这允许不一致的排序的可能性,Java 的当前排序算法有时会遇到这种情况。简而言之,在错误时返回-1 意味着您断言a &lt; bb &lt; a 都是正确的,因为在这两种情况下都会捕获到异常。这在逻辑上是不正确的。排序算法检测到这一点并抛出IllegalArgumentException。请注意,compare 方法在您的堆栈跟踪中不是;这是给Collections.sort的电话。

除了记录异常之外,在您进入程序中的比较步骤之前对其进行处理。如果您必须将字符串解析为整数,请在创建 Employee 对象时执行此操作,以便在您进入程序中的排序步骤之前进行验证。 Comparator 不应该验证数据;它应该只比较数据。

【讨论】:

  • 他们真的应该为此添加一个新的 RuntimeException,因为它本身就具有误导性。 ContractException 会很好,可以更普遍地使用。
  • @davidbak 谁说IllegalArgumentException 只适用于参数的数据,而不适用于行为?以及您将如何执行这种分离
  • @JimGarrison 如果我不得不冒险猜测,有人读了第一句话,将“例外”取消引用到发布堆栈跟踪的那个,然后投了反对票而没有阅读其余部分。
  • @Ant 你没抓住重点。抛出错误是因为这里给 Java 的 &lt;&gt; 的定义本身是矛盾的,以一种可能使排序永远卡住的方式。
  • @davidbak,大概是compare 函数作为sort() 的参数提供。由于sort 要求其比较函数表现良好,因此不一致的比较是该函数的无效参数。
【解决方案2】:

当您显式调用Collections.sort() 时,从内部调用的TimSort.mergeHi() 引发了异常:

在 java.util.TimSort.mergeHi(TimSort.java:868)

您可以在 sort() 周围移动 catch 语句,但结果是排序不会执行或不完整。所以这似乎不是一个好主意。
长话短说:不要违反compareTo() 合同,您不需要捕获任何不再发生的异常。

【讨论】:

    【解决方案3】:

    说明

    java.lang.IllegalArgumentException:比较方法违反其一般合同

    异常不是从您的try 中引发的。这就是为什么它没有被抓住。异常来自代码中的NotifierHTML.java:363,您在其中调用Collection#sort,它使用TimSort 类。然后通过TimSort#mergeHi 方法从TimSort.java:868 抛出异常。

    它告诉您Comparator#compare 方法的实现是错误的。正如其documentation 中所解释的那样,它违反了合同:

    比较它的两个参数的顺序。返回一个整数、或一个整数,因为第一个参数是小于等于,或大于秒。

    实施者必须确保 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 用于所有xy。 (这意味着x.compareTo(y) 必须抛出异常当且仅当 y.compareTo(x) 抛出异常。)

    实现者还必须确保关系是传递的(x.compareTo(y) &gt; 0 &amp;&amp; y.compareTo(z) &gt; 0) 隐含x.compareTo(z) &gt; 0

    最后,实现者必须确保x.compareTo(y) == 0暗示sgn(x.compareTo(z)) == sgn(y.compareTo(z)),对于所有z

    您的实现违反了其中一项要求,而方法检测到了这一点。


    问题的根源

    问题是如果发生错误,您会返回-1。假设您有两个值 firstsecond。并且其中至少有一个会引发异常。

    所以如果你想比较firstsecond,你会得到-1

    compare(first, second) -> -1
    

    这意味着firstsecond。但是如果你用另一种方式比较它,你也会得到-1

    compare(second, first) -> -1
    

    因为两种变体都会引发异常,这会导致您的return -1;。但这意味着您的 compare 方法说:

    first < second
    second < first
    

    两者同时存在,逻辑不正确,违反约定。


    解决方案

    您需要正确定义排序中不可解析内容的放置位置。例如,让我们定义它总是小于任何数字。所以我们想要

    text < number
    

    如果两者都无法解析,我们该怎么办?我们可以说它们是相等的,我们可以按字典顺序比较它们。让我们保持简单,假设任何两个文本都被认为是相等的:

    text = text
    

    我们通过检查哪些参数是不可解析的然后返回正确的值来实现这一点:

    @Override
    public int compare(Employee first, Employee second) {
        Integer firstValue;
        Integer secondValue;
        try {
            firstValue = Integer.parseInt(first.getBadgeNumber());
        } catch (NumberFormatException e) {
            // Could not parse, set null as indicator
            firstValue = null;
        }
        try {
            secondValue = Integer.parseInt(second.getBadgeNumber());
        } catch (NumberFormatException e) {
            // Could not parse, set null as indicator
            secondValue = null;
        }
    
        if (firstValue == null && secondValue != null) {
            // text < number
            return -1;
        }
        if (firstValue != null && secondValue == null) {
            // number > text
            return 1;
        }
        if (firstValue == null && secondValue == null) {
            // text = text
            return 0;
        }
    
        // Both are numbers
        return Integer.compare(firstValue, secondValue);
    }
    

    正如 cmets 中所暗示的,您可以将整个自定义 Comparator 类替换为以下生成相同比较器的语句:

    Comparator<Employee> comp = Comparator.nullsLast(
        Comparator.comparing(e -> tryParseInteger(e.getBadgeNumber())));
    

    与这样的tryParseInteger 方法一起使用:

    public static Integer tryParseInteger(String text) {
        try {
            return Integer.parseInt(text);
        } catch (NumberFormatException e) {
            return null;
        }
    }
    

    【讨论】:

    • 只是一个小评论:我认为比较器可以写成Comparator.nullsLast(Comparator.comparing(e -&gt; tryParseInteger(e.getBadgeNumber())))(相应地实现了一个方法tryParseInteger
    • @Marco13 听起来不错,好地方!
    • @Marco13 鉴于我在开始阅读本文时诅咒 Java 不必要的冗长,我认为您的解决方案是一个重要的补充。
    • 这说明了实际的问题……排序函数不一致。
    • 无关:当 OP 删除了问题时,我正要编辑我的答案并添加该 javadoc。 grmpf。我今天可以充分利用一些赞成/接受。如果有一天我看到……我自己的问题经常因为没有明显原因而被否决……
    【解决方案4】:

    您在此处粘贴的 compare 方法中不会引发该异常。检查堆栈跟踪。里面没有compare 调用。

    【讨论】:

      【解决方案5】:

      虽然情况并非如此,但请记住您可以抛出和捕获Throwable 实例,除了异常之外还有Errors。捕捉它们是可能的,但当它们发生时,不太可能进行任何进一步的工作。

      因此您的 try-catch 不会捕获到错误或除异常之外的任何 Throwable。

      public static void main(String[] args) {
      
          try {
              throw new Error("test exception try-catch");
          } catch (Throwable e) {
              System.out.println("Error caught in throwable catch");
          }
      
          try {
              throw new Error("test exception try-catch");
          } catch (Exception e) {
              System.out.println("Error caught in exception catch");
          }
      }
      

      这将导致:

      Error caught in throwable catch
      Exception in thread "main" java.lang.Error: test exception try-catch
          at ...
      

      【讨论】:

      • 正确,每个Java程序员都应该意识到这一点,但是......与问题无关......
      • 它与标题相关,它是索引和谷歌搜索最多的元素。看看my other answer,这也是不相关的,但这就是 ppl 搜索并去那里的内容。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-29
      • 1970-01-01
      • 2021-03-07
      • 1970-01-01
      • 2020-07-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多