java - 为什么 JDK 中的一些可比较类将比较函数限制为 {−1, 0, 1} 而有些则没有?
问题描述
接口明确要求的唯一返回值java.lang.Comparable<T>
是 0 表示何时T a
和T 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
这是解释吗,应该做任何最简单的事情并给出正确的结果?还是有更深层次的理由来限制某些可比类而不是其他类的差异符号?
解决方案
主要答案
因为实现可以自由选择最适合它们的正值或负值。规范说明了返回值:
负整数、零或正整数,因为此对象小于、等于或大于指定对象。
有人可能会争论这个规范是否是一个明智的决定,但这就是它的定义方式。因此,永远不要依赖比较结果仅为 -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
类型,但这可能会对性能产生影响。
推荐阅读
- django - Django没有这样的表:删除迁移和数据库后的django_site
- postman - 如何通过 POSTMAN 进行远程过程调用?
- c# - 从不带 .ashx 扩展名的 URL 访问 ASHX
- spring-integration - 如何使用 SpEL 读取 Spring 集成路由器中的有效负载和标头内容
- python - 如何使用 python 监控 git 分支?
- linux-kernel - U-boot CONFIG_SYS_TEXT_BASE与SDRAM的关系
- delphi - Delphi - 外部 DLL 和 GetProcAddress
- google-apps-script - Google App Script Deploy as Web App 卡在 Fetching Data 中
- python - 将两列的不同值与熊猫进行比较
- css - 从反应代码中获取父文件夹中的图像