为什么 JDK 中的一些可比较的 类 将比较函数限制为 {−1, 0, 1} 而有些则没有?
Why do some comparable classes in the JDK limit the comparison function to {−1, 0, 1} and some don't?
当 T a
和 T b
相等时,java.lang.Comparable<T>
接口明确要求的唯一 return 值是 0。如果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
的任何事情。我首先注意到 Character.compare()
在 Java 的 Caesar cypher 程序的上下文中的无限制输出,但我发现在本地 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
这里我猜测如果不涉及代理人,或者即使涉及代理人,在 String
的每个字符上只 运行 Character.compare()
会更容易,直到第一个非零结果或到达终点。
这是解释,应该做最简单的事情并给出正确的结果吗?或者是否有更深层次的原因限制某些可比 类 而不是其他差异的符号?
主要回答
因为实现可以自由选择最适合它们的正值或负值。 spec 表示 return 值:
a negative integer, zero, or a positive integer as this object is less
than, equal to, or greater than the specified object.
有人可能会争论这个规范是否是一个明智的决定,但这就是它的定义方式。因此,永远不要依赖于只有 -1、0 或 1 的比较结果,即使对一个特定的 Java 版本进行试验表明该行为 - 它可能会随着下一个版本而改变。
实施答案
这个答案已经在你的问题中找到了,主要是。
有两种典型的比较方式:
- 整数减法:得到的结果不限于-1、0、1。代码简单、优雅、快速。但是可能会溢出,例如对于值 2_000_000_000 - (-2_000_000_000) 在数学上是 4_000_000_000,但对于 32 位整数,结果显示为 -294_967_296,错误地暗示 2_000_000_000 更少比-2_000_000_000。为避免溢出,int 减法适用于大约 +/- 1_000_000_000.
的数字
- 决策:这通常需要一个
if ... else if ... else
结构,其中明确给出了三种情况的 return 值。那么使用-1、0、1是很自然的选择,我不知道使用其他固定值的实现。
因此,减法是 byte 和 char 的有效解决方案,其中 int-based 减法具有足够的保留位,不会发生溢出。因此,这些数据类型及其衍生物更有可能显示 -1、0 和 1 之外的值。
分数class
您正在写关于您正在实施的 Fraction
class。如果可以创建两个 Fraction 实例,而不给出异常,我需要 compareTo()
方法始终给出正确的结果。由于比较分数是一件棘手的事情,因此可以预料到中间结果会溢出。因此,我建议创建一些分子 and/or 分母接近有效性限制的测试用例(无论您将它们定义为什么)。
另一种避免溢出的方法是切换到 unlimited-range BigInteger
类型,但这可能会影响性能。
当 T a
和 T b
相等时,java.lang.Comparable<T>
接口明确要求的唯一 return 值是 0。如果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
的任何事情。我首先注意到 Character.compare()
在 Java 的 Caesar cypher 程序的上下文中的无限制输出,但我发现在本地 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
这里我猜测如果不涉及代理人,或者即使涉及代理人,在 String
的每个字符上只 运行 Character.compare()
会更容易,直到第一个非零结果或到达终点。
这是解释,应该做最简单的事情并给出正确的结果吗?或者是否有更深层次的原因限制某些可比 类 而不是其他差异的符号?
主要回答
因为实现可以自由选择最适合它们的正值或负值。 spec 表示 return 值:
a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
有人可能会争论这个规范是否是一个明智的决定,但这就是它的定义方式。因此,永远不要依赖于只有 -1、0 或 1 的比较结果,即使对一个特定的 Java 版本进行试验表明该行为 - 它可能会随着下一个版本而改变。
实施答案
这个答案已经在你的问题中找到了,主要是。
有两种典型的比较方式:
- 整数减法:得到的结果不限于-1、0、1。代码简单、优雅、快速。但是可能会溢出,例如对于值 2_000_000_000 - (-2_000_000_000) 在数学上是 4_000_000_000,但对于 32 位整数,结果显示为 -294_967_296,错误地暗示 2_000_000_000 更少比-2_000_000_000。为避免溢出,int 减法适用于大约 +/- 1_000_000_000. 的数字
- 决策:这通常需要一个
if ... else if ... else
结构,其中明确给出了三种情况的 return 值。那么使用-1、0、1是很自然的选择,我不知道使用其他固定值的实现。
因此,减法是 byte 和 char 的有效解决方案,其中 int-based 减法具有足够的保留位,不会发生溢出。因此,这些数据类型及其衍生物更有可能显示 -1、0 和 1 之外的值。
分数class
您正在写关于您正在实施的 Fraction
class。如果可以创建两个 Fraction 实例,而不给出异常,我需要 compareTo()
方法始终给出正确的结果。由于比较分数是一件棘手的事情,因此可以预料到中间结果会溢出。因此,我建议创建一些分子 and/or 分母接近有效性限制的测试用例(无论您将它们定义为什么)。
另一种避免溢出的方法是切换到 unlimited-range BigInteger
类型,但这可能会影响性能。