为什么 compareTo return 是一个整数
Why does compareTo return an integer
我最近在SO聊天中看到一个讨论,但没有明确的结论,所以我最后在那里问了。
这是出于历史原因还是为了与其他语言保持一致?查看各种语言compareTo
的签名时,return是一个int
。
为什么它不 return 枚举。例如在 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# 了)但是它可以通过额外的 class 来解决,例如:
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
尊重比较器接口的约定(采用两个整数和 return 一个整数)。
显然这两种情况的结果都不是预期的。如果 return 类型是 Compare
它会导致编译错误从而迫使你实现 "correct" 行为(或者至少你知道你在做什么)。
这种 return 类型丢失了很多语义(并且可能会导致一些难以发现的错误),那么为什么要这样设计呢?
回复这是由于性能原因。
如果您经常需要比较 int,您可以 return 以下内容:
事实上的比较通常return作为减法。
举个例子
public class MyComparable implements Comparable<MyComparable> {
public int num;
public int compareTo(MyComparable x) {
return num - x.num;
}
}
[此答案适用于 C#,但它可能在某种程度上也适用于 Java。]
这是出于历史、性能和可读性方面的原因。它可能会在两个地方提高性能:
- 进行比较的地方。通常你可以 return "(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)
...
IMO 的可读性仍然很差 - 而且它的效率仍然较低,因为它执行两个比较操作而不是一个(尽管我坦率地承认这种性能差异很可能很少重要)。
正如 raznagul 在下面指出的那样,您实际上只需进行一次比较就可以做到:
if (lhs.CompareTo(rhs) != CompareResult.GreaterThan)
...
因此您可以使其相当高效 - 但当然,可读性仍然受到影响。 ... != GreaterThan
不如 ... <=
清楚
(当然,如果使用枚举,则无法避免将比较结果转换为枚举值的开销。)
所以这样做主要是为了可读性,但在某种程度上也是为了效率。
最后,正如其他人所说,这也是出于历史原因。像 C 的 strcmp()
和 memcmp()
这样的函数总是 returned 整数。
汇编程序比较指令也倾向于以类似的方式使用。
例如,要在 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() 中看到与 return 值的明显比较。
附录:
这是一个示例,它表明使用从 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 的评论。)
让我们坚持赤裸裸的事实,绝对最少的挥手 and/or unnecessary/irrelevant/implementation 相关细节。
您自己已经知道,compareTo
和 Java 一样大(Since: JDK1.0
来自 Integer JavaDoc); Java 1.0 旨在让 C/C++ 开发人员熟悉,并模仿了它的许多设计选择,无论好坏。此外,Java 具有 向后兼容性 政策 - 因此,一旦在核心库中实施,该方法 almost 必然会保留在其中永远。
至于 C/C++ - strcmp
/memcmp
,它的存在时间与 string.h 一样长,因此基本上与 C 标准库一样长,return 完全相同的值(或者更确切地说,compareTo
returns 与 strcmp
/memcmp
相同的值) - 参见例如C ref - strcmp。在 Java 成立之初,这样做是合乎逻辑的。当时 Java 中没有任何枚举,没有泛型等(所有这些都出现在 >= 1.5)
strcmp
的 return 值的决定非常明显 - 首先,您可以比较得到 3 个基本结果,因此为 "bigger" 选择 +1,- 1 代表 "smaller" 和 0 代表 "equal" 是合乎逻辑的事情。此外,正如所指出的,您可以通过减法轻松获得该值,并且 returning int
允许在进一步的计算中轻松使用它(以传统的 C 类型不安全方式),同时还允许高效单操作实现。
如果您 need/want 使用基于 enum
的类型安全比较接口 - 您可以自由地这样做,但是由于 strcmp
returning +1
/0
/-1
与当代编程一样古老,它实际上 确实 传达语义,以相同的方式 null
可以被解释为 unknown/invalid value
或越界 int 值(例如,为纯正质量提供的负数)可以被解释为错误代码。也许这不是最好的编码实践,但它肯定有其优点,并且仍然常用,例如在 C.
另一方面,询问 "why the standard library of language XYZ does conform to legacy standards of language ABC" 本身没有实际意义,因为它只能由实现它的设计语言准确回答。
TL;DR 之所以这样,主要是因为遗留原因在遗留版本中是这样做的,POLA 是为了 C 程序员,并且保持这种方式用于再次向后兼容和 POLA。
作为旁注,我认为这个问题(以目前的形式)过于宽泛,无法准确回答,高度基于意见,并且由于直接询问 设计模式 & 语言架构.
这种做法来自于以这种方式比较整数,并在字符串的第一个不匹配字符之间使用减法。
请注意,这种做法对于部分可比较的事物是危险的,而使用 -1 表示一对事物是不可比较的。这是因为它可能会造成 a < b 和 b < a 的情况(应用程序可能会使用它来定义 "incomparable")。这种情况会导致无法正确终止的循环。
具有值 {lt,eq,gt,incomparable} 的枚举会更正确。
我的理解是这样做是因为你可以对结果进行排序(即操作是自反和传递的)。例如,如果您有三个对象(A、B、C),您可以比较 A->B 和 B->C,并使用结果值对它们进行正确排序。有一个隐含的假设,如果 A.compareTo(B) == A.compareTo(C) 那么 B==C.
请参阅 java 的 comparator 文档。
我最近在SO聊天中看到一个讨论,但没有明确的结论,所以我最后在那里问了。
这是出于历史原因还是为了与其他语言保持一致?查看各种语言compareTo
的签名时,return是一个int
。
为什么它不 return 枚举。例如在 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# 了)但是它可以通过额外的 class 来解决,例如:
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
尊重比较器接口的约定(采用两个整数和 return 一个整数)。
显然这两种情况的结果都不是预期的。如果 return 类型是 Compare
它会导致编译错误从而迫使你实现 "correct" 行为(或者至少你知道你在做什么)。
这种 return 类型丢失了很多语义(并且可能会导致一些难以发现的错误),那么为什么要这样设计呢?
回复这是由于性能原因。 如果您经常需要比较 int,您可以 return 以下内容:
事实上的比较通常return作为减法。
举个例子
public class MyComparable implements Comparable<MyComparable> {
public int num;
public int compareTo(MyComparable x) {
return num - x.num;
}
}
[此答案适用于 C#,但它可能在某种程度上也适用于 Java。]
这是出于历史、性能和可读性方面的原因。它可能会在两个地方提高性能:
- 进行比较的地方。通常你可以 return "(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)
...
IMO 的可读性仍然很差 - 而且它的效率仍然较低,因为它执行两个比较操作而不是一个(尽管我坦率地承认这种性能差异很可能很少重要)。
正如 raznagul 在下面指出的那样,您实际上只需进行一次比较就可以做到:
if (lhs.CompareTo(rhs) != CompareResult.GreaterThan)
...
因此您可以使其相当高效 - 但当然,可读性仍然受到影响。 ... != GreaterThan
不如 ... <=
(当然,如果使用枚举,则无法避免将比较结果转换为枚举值的开销。)
所以这样做主要是为了可读性,但在某种程度上也是为了效率。
最后,正如其他人所说,这也是出于历史原因。像 C 的 strcmp()
和 memcmp()
这样的函数总是 returned 整数。
汇编程序比较指令也倾向于以类似的方式使用。
例如,要在 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() 中看到与 return 值的明显比较。
附录:
这是一个示例,它表明使用从 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 的评论。)
让我们坚持赤裸裸的事实,绝对最少的挥手 and/or unnecessary/irrelevant/implementation 相关细节。
您自己已经知道,compareTo
和 Java 一样大(Since: JDK1.0
来自 Integer JavaDoc); Java 1.0 旨在让 C/C++ 开发人员熟悉,并模仿了它的许多设计选择,无论好坏。此外,Java 具有 向后兼容性 政策 - 因此,一旦在核心库中实施,该方法 almost 必然会保留在其中永远。
至于 C/C++ - strcmp
/memcmp
,它的存在时间与 string.h 一样长,因此基本上与 C 标准库一样长,return 完全相同的值(或者更确切地说,compareTo
returns 与 strcmp
/memcmp
相同的值) - 参见例如C ref - strcmp。在 Java 成立之初,这样做是合乎逻辑的。当时 Java 中没有任何枚举,没有泛型等(所有这些都出现在 >= 1.5)
strcmp
的 return 值的决定非常明显 - 首先,您可以比较得到 3 个基本结果,因此为 "bigger" 选择 +1,- 1 代表 "smaller" 和 0 代表 "equal" 是合乎逻辑的事情。此外,正如所指出的,您可以通过减法轻松获得该值,并且 returning int
允许在进一步的计算中轻松使用它(以传统的 C 类型不安全方式),同时还允许高效单操作实现。
如果您 need/want 使用基于 enum
的类型安全比较接口 - 您可以自由地这样做,但是由于 strcmp
returning +1
/0
/-1
与当代编程一样古老,它实际上 确实 传达语义,以相同的方式 null
可以被解释为 unknown/invalid value
或越界 int 值(例如,为纯正质量提供的负数)可以被解释为错误代码。也许这不是最好的编码实践,但它肯定有其优点,并且仍然常用,例如在 C.
另一方面,询问 "why the standard library of language XYZ does conform to legacy standards of language ABC" 本身没有实际意义,因为它只能由实现它的设计语言准确回答。
TL;DR 之所以这样,主要是因为遗留原因在遗留版本中是这样做的,POLA 是为了 C 程序员,并且保持这种方式用于再次向后兼容和 POLA。
作为旁注,我认为这个问题(以目前的形式)过于宽泛,无法准确回答,高度基于意见,并且由于直接询问 设计模式 & 语言架构.
这种做法来自于以这种方式比较整数,并在字符串的第一个不匹配字符之间使用减法。
请注意,这种做法对于部分可比较的事物是危险的,而使用 -1 表示一对事物是不可比较的。这是因为它可能会造成 a < b 和 b < a 的情况(应用程序可能会使用它来定义 "incomparable")。这种情况会导致无法正确终止的循环。
具有值 {lt,eq,gt,incomparable} 的枚举会更正确。
我的理解是这样做是因为你可以对结果进行排序(即操作是自反和传递的)。例如,如果您有三个对象(A、B、C),您可以比较 A->B 和 B->C,并使用结果值对它们进行正确排序。有一个隐含的假设,如果 A.compareTo(B) == A.compareTo(C) 那么 B==C.
请参阅 java 的 comparator 文档。