为什么compareTo返回一个整数

时间:2015-03-30 09:52:23

标签: java c# comparator comparable

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

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

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

enum CompareResult {LessThan, Equals, GreaterThan};

和:

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

在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() {}
}  

interface Comparable<T> {
    Compare compareTo(T obj);
}

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

例如在C#中,

l.Sort(delegate(int x, int y)
        {
            return Math.Min(x, y);
        });

及其在Java 8中的双胞胎,

l.sort(Integer::min);

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

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

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

5 个答案:

答案 0 :(得分:21)

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

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

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

    if (lhs.CompareTo(rhs) <= 0)
        ...
    

    使用枚举,如下所示:

    if (lhs.CompareTo(rhs) == CompareResult.LessThan ||
        lhs.CompareTo(rhs) == CompareResult.Equals)
        ...
    

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

    var compareResult = lhs.CompareTo(rhs);
    
    if (compareResult == CompareResult.LessThan || compareResult == CompareResult.Equals)
        ...
    

    它的可读性仍然低得多 - 而且它的效率仍然较低,因为它进行了两次比较操作而不是一次(尽管我承认这很可能是这样的表现差异很少会产生影响)。

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

    if (lhs.CompareTo(rhs) != CompareResult.GreaterThan)
        ...
    

    所以你可以使它相当有效 - 但当然,可读性仍然受到影响。 ... != GreaterThan不如... <=

    那么清晰

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

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

    最后,正如其他人所提到的,这也是出于历史原因。 C&#39 {s} strcmp()memcmp()等函数始终返回。

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

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

    CMP AX, BX ; 
    JLE lessThanOrEqual ; jump to lessThanOrEqual if AX <= BX
    

    CMP AX, BX
    JG greaterThan ; jump to greaterThan if AX > BX
    

    CMP AX, BX
    JE equal      ; jump to equal if AX == BX
    

    您可以看到与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!
    

    显然这是因为算术溢出了。如果您为构建启用了checked,则上面的代码实际上会抛出异常。

    出于这个原因,最好避免优化使用减法来实现比较。 (见下文Eric Lippert的评论。)

答案 1 :(得分:5)

让我们坚持使用绝对最低限度的手工操作和/或不必要/不相关/实现相关的细节。

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

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

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

如果您需要/想要使用基于int的类型安全比较界面 - 您可以自由地执行此操作,但由于enum约定返回strcmp / {{ 1}} / +1与当代编程一样古老,它实际上 传达语义,同样0可以被解释为-1或者超出范围的int值(例如,为仅正质量提供的负数)可以被解释为错误代码。也许它不是最好的编码实践,但它肯定有它的优点,并且仍然常用,例如在C.

另一方面,询问&#34;为什么XYZ语言的标准库确实符合ABC语言的传统标准。本身没有实际意义,因为它只能通过设计实现它的语言来准确回答。

TL; DR 这样做主要是因为遗留原因在旧版本中以这种方式完成,而对于C程序员来说是POLA,并且这样做是为了倒退 - 兼容性&amp; POLA,再次。

作为旁注,我认为这个问题(目前的形式)过于宽泛,无法准确回答,高度基于意见,并且由于直接询问设计模式而在SO上出现临界主题&amp; 语言架构

答案 2 :(得分:1)

这种做法来自于以这种方式比较整数,并使用字符串的第一个非匹配字符之间的减法。

请注意,对于使用-1来表示一对事物无法比拟的部分可比较的事物,这种做法很危险。这是因为它可以创建一个&lt; b和b&lt; a(应用程序可能用来定义“无法比拟的”)。这种情况可能导致循环无法正确终止。

值为{lt,eq,gt,incomparable}的枚举会更正确。

答案 3 :(得分:-2)

回复这是出于性能原因。 如果您需要经常比较 int ,您可以返回以下内容:

事实上,比较通常作为减法返回。

作为一个例子

public class MyComparable implements Comparable<MyComparable> {
    public int num;

    public int compareTo(MyComparable x) {
        return num - x.num;
    }
}

答案 4 :(得分:-2)

我的理解是这样做是因为你可以订购结果(即操作是反身和传递的)。例如,如果您有三个对象(A,B,C),则可以比较A-> B和B-> C,并使用结果值对它们进行正确排序。有一个隐含的假设是,如果A.compareTo(B)== A.compareTo(C)则B == C.

请参阅java的comparator文档。