首页 > 解决方案 > 为什么 JDK 中的一些可比较类将比较函数限制为 {−1, 0, 1} 而有些则没有?

问题描述

接口明确要求的唯一返回值java.lang.Comparable<T>是 0 表示何时T aT b相等。如果a小于b,则compare(a, b)必须是负数,不一定是 -1,并且compare(b, a)必须是正数,不一定是 1。

然而 JDK 中的一些可比较类以这种方式精确地限制了比较函数的输出,例如,

scala> (65 to 90).map(n => java.lang.Integer.compare(77, n))
res19: IndexedSeq[Int] = Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1)

有些没有,例如,

scala> ('A' to 'Z').map(ch => java.lang.Character.compare('M', ch))
res10: IndexedSeq[Int] = Vector(12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 
-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13)

我很清楚 RDN 有点晦涩的案例(来自javax.naming.ldap),但我没想到会遇到来自java.lang. 我首先注意到在 Java 中的 Caesar cypher 程序上下文中的无限制输出Character.compare(),但我发现在本地 Scala REPL 中运行像这样的“实验”更容易。

当我编写我的实现时Fraction,我遵循了Integer而不是Character

scala> val fractA = new fractions.Fraction(65, 128)
fractA: fractions.Fraction = 65/128

scala> val fractB = new fractions.Fraction(90, 128)
fractB: fractions.Fraction = 45/64

scala> fractA to fractB
res20: fractions.FractionRange = 65/128 to 45/64

scala> val fractC = new fractions.Fraction(77, 128)
fractC: fractions.Fraction = 77/128

scala> res20.map(_.compare(fractC))
res21: IndexedSeq[Int] = Vector(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)

scala> res20.map(fractC.compare)
res22: IndexedSeq[Int] = Vector(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1)

scala> res19 == res22
res23: Boolean = true

在 的情况下Fraction,这样实现是没有问题的。实际上,不这样做会更麻烦。分子和分母的类型是long,所以如果差的分子稍微超出 的范围,就会出现问题int。通过使用Long.signum(),我将错误结果的可能性减少到一小组边缘情况。

由于char映射到 的一半范围int,我想String不将结果限制compare()为 {−1, 0, 1} 会更容易。

scala> "Hello, World!" compare "Hello, world?"
res30: Int = -32

在这里,我猜测如果不涉及代理,或者即使涉及代理,在第一个非零结果或到达末尾之前运行Character.compare()每个字符会更容易。String

这是解释吗,应该做任何最简单的事情并给出正确的结果?还是有更深层次的理由来限制某些可比类而不是其他类的差异符号?

标签: javacomparable

解决方案


主要答案

因为实现可以自由选择最适合它们的正值或负值。规范说明了返回值:

负整数、零或正整数,因为此对象小于、等于或大于指定对象。

有人可能会争论这个规范是否是一个明智的决定,但这就是它的定义方式。因此,永远不要依赖比较结果仅为 -1、0 或 1,即使尝试使用一个特定的 Java 版本会显示出这种行为——它可能会随着下一个版本而改变。

实施答案

这个答案已经在你的问题中找到了,主要是。

比较典型的实现方式有两种:

  • 整数减法:给出的结果不限于-1、0、1。代码简单、优雅、快速。但是可能存在溢出,例如对于值 2_000_000_000 - (-2_000_000_000) 数学上是 4_000_000_000,但是对于 32 位 int,结果显示为 -294_967_296,错误地暗示 2_000_000_000 小于 -2_000_000_000。为避免溢出,int 减法适用于大约 +/- 1_000_000_000 的数字。
  • 决策:这通常需要一个if ... else if ... else结构,其中明确给出三种情况的返回值。那么使用-1、0和1是很自然的选择,我不知道使用其他固定值的实现。

因此,减法是 byte 和 char 的有效解决方案,其中基于 int 的减法具有足够的保留位,不会发生溢出。因此,这些数据类型及其衍生物更有可能显示 -1、0 和 1 之外的值。

分数类

你正在写一个Fraction你正在实施的课程。如果可以创建两个 Fraction 实例而不给出异常,我会要求该compareTo()方法始终提供正确的结果。由于分数的比较是一件棘手的事情,因此可以预期中间结果的溢出。因此,我建议创建一些分子和/或分母接近有效限制的测试用例(无论您定义它们是什么)。

另一种避免溢出的方法是切换到无限范围BigInteger类型,但这可能会对性能产生影响。


推荐阅读