为什么比较返回一个整数
我最近在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
,则会导致编译错误,从而迫使您实现"正确"行为(或者至少您知道自己在做什么)。
此返回类型丢失了很多语义(并且可能会导致难以找到的错误),那么为什么要这样设计它?
解决方案
[此答案适用于C#,但在某种程度上可能也适用于JAVA。]
这是出于历史、性能和可读性的原因。它可能会在两个方面提高性能:
- 实现比较的位置。通常,您只需返回"(lhs-rhs)"(如果值是数值类型)。但这可能很危险:请参见下面的内容!
- 调用代码可以使用
<=
和>=
来自然地表示对应的比较。与使用枚举相比,这将使用单个IL(因此使用处理器)指令(尽管有一种方法可以避免枚举的开销,如下所述)。
例如,我们可以按如下方式检查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的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的评论。)
相关文章