为什么compareTo返回一个整数

use*_*315 22 c# java comparable comparator

我最近在SO聊天中看到了一个讨论,但没有明确的结论,所以我最后在那里问.

这是出于历史原因还是与其他语言的一致性?在查看compareTo各种语言的签名时,它会返回一个int.

为什么它不返回枚举.例如在C#中我们可以这样做:

enum CompareResult {LessThan, Equals, GreaterThan};
Run Code Online (Sandbox Code Playgroud)

并且:

public CompareResult CompareTo(Employee other) {
    if (this.Salary < other.Salary) {
         return CompareResult.LessThan;
    }
    if (this.Salary == other.Salary){
        return CompareResult.Equals;
    }
    return CompareResult.GreaterThan;
}
Run Code Online (Sandbox Code Playgroud)

在Java中,枚举是在这个概念之后引入的(我不记得有关C#)但它可以通过额外的类来解决,例如:

public final class CompareResult {
    public static final CompareResult LESS_THAN = new Compare();
    public static final CompareResult EQUALS = new Compare();
    public static final CompareResult GREATER_THAN = new Compare();

    private CompareResult() {}
}  
Run Code Online (Sandbox Code Playgroud)

interface Comparable<T> {
    Compare compareTo(T obj);
}
Run Code Online (Sandbox Code Playgroud)

我问这个是因为我不认为一个int代表数据的语义.

例如在C#中,

l.Sort(delegate(int x, int y)
        {
            return Math.Min(x, y);
        });
Run Code Online (Sandbox Code Playgroud)

在Java 8中它的双胞胎,

l.sort(Integer::min);
Run Code Online (Sandbox Code Playgroud)

编译两者都因为Min/min尊重比较器接口的契约(取两个int并返回一个int).

显然,两种情况下的结果都不是预期的结果.如果返回类型是Compare因为它会导致编译错误,从而迫使您实现"正确"行为(或者至少您知道自己在做什么).

这种返回类型会丢失很多语义(并且可能会导致一些难以找到的错误),那么为什么要这样设计呢?

Mat*_*son 21

[这个答案适用于C#,但它在某种程度上也可能适用于Java.]

这是出于历史,性能和可读性的原因.它可能会在两个地方提高性能:

  1. 比较实施的地方.通常你可以只返回"(lhs - rhs)"(如果值是数字类型).但这可能很危险:见下文!
  2. 调用代码可以使用<=>=自然地表示相应的比较.与使用枚举相比,这将使用单个IL(因此处理器)指令(尽管有一种方法可以避免枚举的开销,如下所述).

例如,我们可以检查lhs值是否小于或等于rhs值,如下所示:

if (lhs.CompareTo(rhs) <= 0)
    ...
Run Code Online (Sandbox Code Playgroud)

使用枚举,看起来像这样:

if (lhs.CompareTo(rhs) == CompareResult.LessThan ||
    lhs.CompareTo(rhs) == CompareResult.Equals)
    ...
Run Code Online (Sandbox Code Playgroud)

这显然不太可读,而且效率也很低,因为它进行了两次比较.您可以通过使用临时结果来解决效率低下问题:

var compareResult = lhs.CompareTo(rhs);

if (compareResult == CompareResult.LessThan || compareResult == CompareResult.Equals)
    ...
Run Code Online (Sandbox Code Playgroud)

它仍然是一个不太可读的IMO - 它仍然效率较低,因为它做了两个比较操作而不是一个(尽管我自由地承认这种性能差异很可能很少).

正如raznagul在下面指出的那样,你只需要进行一次比较即可实现:

if (lhs.CompareTo(rhs) != CompareResult.GreaterThan)
    ...
Run Code Online (Sandbox Code Playgroud)

所以你可以使它相当有效 - 但当然,可读性仍然受到影响.... != GreaterThan不是那么清楚... <=

(如果你使用枚举,你当然无法避免将比较结果转换为枚举值的开销.)

因此,这主要是出于可读性的原因,但在某种程度上也是出于效率的原因.

最后,正如其他人所提到的,这也是出于历史原因.功能像C的strcmp()memcmp()始终返回整数.

汇编程序比较指令也倾向于以类似的方式使用.

例如,要比较x86汇编程序中的两个整数,可以执行以下操作:

CMP AX, BX ; 
JLE lessThanOrEqual ; jump to lessThanOrEqual if AX <= BX
Run Code Online (Sandbox Code Playgroud)

要么

CMP AX, BX
JG greaterThan ; jump to greaterThan if AX > BX
Run Code Online (Sandbox Code Playgroud)

要么

CMP AX, BX
JE equal      ; jump to equal if AX == BX
Run Code Online (Sandbox Code Playgroud)

您可以看到与CompareTo()返回值的明显比较.

附录:

这是一个例子,它表明使用从lhs中减去rhs来获得比较结果的技巧并不总是安全的:

int lhs = int.MaxValue - 10;
int rhs = int.MinValue + 10;

// Since lhs > rhs, we expect (lhs-rhs) to be +ve, but:

Console.WriteLine(lhs - rhs); // Prints -21: WRONG!
Run Code Online (Sandbox Code Playgroud)

显然这是因为算术溢出了.如果你checked打开了构建,上面的代码实际上会抛出异常.

因此,最好避免优化使用减法来实现比较.(见下文Eric Lippert的评论.)

  • 你不需要两个比较:`lhs.CompareTo(rhs)!= CompareResult.GreaterThan` (3认同)
  • @MatthewWatson:很好的例子!我个人会修改你的建议,只是简单地"使用减法来实现比较是最好的." 首先,它只是一个*优化*,如果它是*正确*和*比一些替代品更好的执行*,这两者通常都不适用于减法技巧.还没有人编写过一个程序,它在市场上的巨大成功是由于它使用减法而不是比较来实现比较.第二,即使数值合适,它仍然不是一个好主意. (3认同)
  • 我注意到"使用减法"技巧经常被危险地打破.对于许多整数类型,您可以找到两个数字,例如`x <y`和`x - y> 0`. (2认同)

vax*_*uis 5

让我们坚持一个简单的事实,绝对最少的手工和/或不必要/不相关/实现相关的细节.

正如你已经想到的那样,compareTo它和Java一样古老(Since: JDK1.0来自Integer JavaDoc); Java 1.0被设计为C/C++开发人员熟悉,并且模仿了它的许多设计选择,无论好坏.此外,Java具有向后兼容性策略 - 因此,一旦在核心库中实现,该方法几乎必将永远保留在其中.

至于C/C++ - strcmp/ memcmp,它与string.h一样存在,所以基本上只要C标准库,返回完全相同的值(或者更确切地说,compareTo返回与strcmp/ 相同的值memcmp) - 参见例如C ref - strcmp.在Java开始时,这种方式是合乎逻辑的事情.当时Java中没有任何枚举,没有泛型等等(所有这些都是> = 1.5)

返回值的决定strcmp是非常明显的 - 首先,你可以得到3个基本结果进行比较,因此为"更大"选择+1,为"更小"选择-1,为"相等"选择0是合乎逻辑的事情.做.此外,正如所指出的,您可以通过减法轻松获得值,并且返回int允许在进一步计算中使用它(以传统的C类型不安全的方式),同时还允许有效的单操作实现.

如果您需要/想要使用enum基于类型的安全比较界面 - 您可以自由地这样做,但由于strcmp返回+1/ 0/ 的惯例-1与当代编程一样古老,它实际上确实传达了语义含义,同样null可以解释as unknown/invalid value或out of bounds int值(例如,为仅正质量提供的负数)可以解释为错误代码.也许它不是最好的编码实践,但它肯定有它的优点,并且仍然常用于例如C.

另一方面,问"为什么XYZ语言的标准库确实符合ABC语言的遗留标准"本身没有实际意义,因为它只能通过设计实现它的语言来准确回答.

TL; DR就是这样,主要是因为遗留原因以及C语言程序员的POLA以遗留版本的方式完成,并且再次以向后兼容性和POLA保持这种方式.

作为旁注,我认为这个问题(目前的形式)过于宽泛,无法准确回答,高度基于意见,以及由于直接询问设计模式语言架构而在SO上的偏离主题.

  • @PaŭloEbermann你的推理是错误的.我回答了这个问题,因为OP问道; 我标记它是因为我必须自由地重新解释这个问题甚至能够回答它.我赞成它,因为它对编码和调试没有帮助 - 对于Programmers.SE来说这是一个很好的问题,但不是SO.因此,我认为没有理由不回答我认为这里的边界问题.没有回答错误的问题总是比试图回答它更糟糕 - 你无法通过任何其他方式证明问题存在缺陷,而不是试图回答问题. (2认同)