Java:为什么无法在Comparator中抛出异常?

6
直接的答案是因为Comparator.compare接口规定不会抛出异常。但是为什么呢?
或者换句话说:我的Comparator必须依赖于一个可能会抛出异常的函数。理论上,这种情况不应该发生。但如果发生了,我希望它能跳出我正在使用Collections.sort的整个函数(其中包含Comparator)。也就是说,我希望它表现得就像发生了未处理的异常一样。
看起来这在自然方式下不可能实现(因为如果接口规定不能抛出异常,那么就不能抛出异常)。
我该如何解决这个问题?用丑陋的try/catch语句并打印出异常,希望我能识别它吗?这似乎是一种相当丑陋的方式。

2
你考虑过抛出 RuntimeException 吗?(可能需要包装已检查的异常) - Kirk Woll
8个回答

15

即使在一个没有明确声明抛出异常的方法中,你仍然可以抛出一个 RuntimeException 或其派生类。


6
在这种情况下,我会重新抛出一个 AssertionError,因为你假设异常不可能被引发。不要忘记使用 initCause() 方法来传播信息(AssertionError 没有接受 Throwable 的构造函数)。

2
AssertionError有一个接受Object的构造函数,如果它是Throwable,则将用作原因。(随机附注:我相信他们正在JDK7中改进这个。) - ColinD

3

这是Comparator.compare方法的合同。如果您想使用它,只需遵循规则,不要从中抛出已检查异常 :) 同时,您可以抛出未经检查的异常(RuntimeException或其子类),这不会违反合同。


2

1

您在问题标题和问题正文中提出了不同的问题。

您没有清楚地说明compare()方法使用的支持异常的函数为什么会抛出异常。这可能是因为集合中存在某些无法比较的对象(如NaN数值),或者是因为存在某些无法相互比较的对象对。

为什么不能在Comparator中抛出异常?

我猜测Comparator.compare()没有设计为抛出已检查的异常,因为:

  1. 假设您希望比较/排序的任何项目都是可比较的。

  2. 如果Comparator.compare()可能会抛出某种预期的(即已检查的)异常,那么我可以想象出几种不良情况:

    a. 排序可能会中止,因为其中有某种无法比较的对象-可能的响应是删除无法比较的对象并再次尝试排序

    b. 对同一组对象的不同排序顺序进行多次排序时,有时可能会因为在排序过程中出现了一对无法比较的对象而导致异常,有时则可能成功

当然,这只是我的猜测。

我该如何解决这个问题?

我假设您的Comparator.compare()使用可能会引发异常的函数抛出异常的原因是集合中存在无法比较的对象(例如NaN数值)。选项包括:

  1. 对列表的副本进行排序,删除不可比较对象。

  2. 抛出未经检查(运行时)异常以中止排序。如果不确定要做什么,则执行上述第1步。

  3. 遵循 NaN 方法,使这些对象出现在开头或结尾。

    NaN 值通常无法与其他值进行比较,但在排序期间,比较器定义了自己的总排序,以便 NaN 值最终出现在已排序集合的末尾。

    http://download.oracle.com/javase/1.4.2/docs/api/java/util/Arrays.html#sort(double[])

    ... < 关系不能为所有浮点值提供完全顺序;... NaN 值既不小于、也不大于、也不等于任何浮点值,甚至是它本身。

    ... 为了允许排序进行,... 此方法使用 Double.compareTo(java.lang.Double) 强制实施的总排序。

    ... 这种排序与 < 关系不同,其中... NaN 被认为大于任何其他浮点值。为了排序的目的,所有 NaN 值都被视为等效和相等。

    为此,请编写您的 Comparator.compare() 代码,使任何不可比较的对象始终比任何可比较的对象更大,并且它始终等于任何其他不可比较的对象。


0

您可以使用多种技巧重新抛出已检查异常并避免编译错误。最简单的方法是;

try {
   // something
} catch (Exception e) {
   Thread.currentThread().stop(e);
}

然而,由于编译器不知道您已经这样做了。如果您不小心,可能会让自己和编译器混淆。闭包的一个目标是正确处理类似比较器中的已检查异常(而其他人则希望它们消失)。


0

有两种解决方法:

  1. 捕获异常并在 java.lang.RuntimeException() 中抛出,或者
  2. 捕获异常并使用 Log4J 或 SLF4J(或任何您感到舒适的日志记录器工厂)进行记录。

比较器的 compare 方法的契约不会抛出异常。


2
我建议不要记录错误并继续执行。这不是你应该忽略的错误类型。 - gawi
我同意您的观点,但您只是在进行一些比较操作,因此,在本质上,比较方法假定不应抛出任何异常。此外,通过记录异常,您可以在文件中获得堆栈跟踪,以显示异常发生的位置。 - Buhake Sindi
记录日志的方式可以告诉你第一个异常发生在哪里,因为当异常被忽略时很可能会引发更多的异常(是的,记录它并继续工作等同于忽略)。 - whiskeysierra
此外,您正在比较两个对象(这将有效地使用对象的“equals()”方法),因此抛出异常的可能性很小。记录异常并继续工作有助于您稍后意识到已抛出异常。如果抛出异常,程序员可以简单地返回-1。日志记录在文件中提供了更多信息。 - Buhake Sindi

-2
本文介绍了为什么在Comparator接口内部抛出RuntimeException是一个不好的想法,并展示了两种良好实践的样例源代码,以处理在不支持它们的接口中处理已检查异常:1)将问题分成两个部分,或2)使用自己的支持已检查异常的比较器。

https://www.ibm.com/developerworks/library/j-ce/index.html

Comparator 接口定义了一个契约。该契约不允许此方法抛出运行时异常(除非违反通用类型安全性资格作为调用代码中的错误)。合法使用此比较器的方法需要它来比较两个文件,而不会抛出任何异常。它们将无法处理从 compare() 意外冒泡的异常。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接