我最近在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
,则会导致编译错误,从而迫使您实现“正确”行为(或者至少您知道自己在做什么)。
这种返回类型会丢失很多语义(可能会导致一些难以找到的错误),为什么要这样设计呢?
答案 0 :(得分:21)
[这个答案适用于C#,但它在某种程度上也可能适用于Java。]
这是出于历史,性能和可读性的原因。它可能会在两个地方提高性能:
<=
和>=
自然地表示相应的比较。与使用枚举相比,这将使用单个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&#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文档。