为什么C#和Java使用引用相等作为'=='的默认值?


32

我已经思考了一段时间,为什么Java和C#(我敢肯定其他语言)默认引用相等==

在我做的编程中(当然,这只是编程问题的一小部分),在比较对象而不是引用相等时,我几乎总是想要逻辑相等。我试图思考为什么这两种语言都走这条路线而不是颠倒它,并具有==逻辑上的相等性,并.ReferenceEquals()用于引用相等性。

显然,使用引用相等很容易实现,并且行为非常一致,但是似乎并不适合我今天看到的大多数编程实践。

我不想对尝试实现逻辑比较的问题一无所知,并且必须在每个类中都实现它。我也意识到这些语言是很久以前设计的,但总的问题仍然存在。

默认情况下,我只是缺少一些主要好处,还是默认行为应该是逻辑相等,并且默认返回引用相等,而该类不存在逻辑相等似乎合理吗?


3
因为变量是引用?由于变量就像指针一样起作用,因此像这样比较它们是有意义的
Daniel Gratzer 2013年

C#对诸如结构之类的值类型使用逻辑相等性。但是对于不同引用类型的两个对象,“默认逻辑相等性”应该是什么呢?还是对于两个对象,其中一个是从B继承的类型A?总是像结构一样“假”吗?即使您有两次引用相同的对象,首先引用为A,然后引用为B?对我来说没有多大意义。
布朗

3
换句话说,您是否在问为什么在C#中,如果您重写Equals(),它不会自动更改的行为==
svick

Answers:


29

C#这样做是因为Java做到了。Java之所以这样做,是因为Java不支持运算符重载。由于必须为每个类重新定义值相等性,因此它不能是运算符,而必须是方法。IMO这是一个糟糕的决定。它的编写和读取a == b都比容易得多a.equals(b),并且对具有C或C ++经验的程序员来说更自然,但是a == b几乎总是错误的。使用所需的==位置.equals所产生的错误浪费了无数的程序员小时。


7
我认为操作员超载的支持者与批评者一样多,因此,我绝对不会说“这是一个糟糕的决定”。示例:在我从事的C ++项目中,我们已经==对许多类进行了重载,几个月前,我发现一些开发人员不知道==实际在做什么。当某些构造的语义不明显时,总是存在这种风险。该equals()符号告诉我,我正在使用自定义方法,因此必须在某个地方查找它。底线:我认为运算符重载通常是一个未解决的问题。
Giorgio 2013年

9
我会说Java没有用户定义的运算符重载。Java中大量运算符都有双重(重载)含义。+例如,看一下,它同时执行(数字值的)加法和字符串的串联。
Joachim Sauer 2013年

14
a == b由于C不支持用户定义的运算符重载,对于具有C经验的程序员来说,怎么会更自然?(例如,比较字符串的C方法是strcmp(a, b) == 0,而不是a == b。)
svick 2013年

这基本上就是我的想法,但是我认为我会请那些有更多经验的人确保我没有遗漏明显的东西。
拉链

4
@svick:在C中,没有字符串类型,也没有任何引用类型。字符串操作通过完成char *。在我看来,比较两个指针是否相等与字符串比较并不相同。
凯文·克莱恩

15

简短的答案:一致性

不过,为了正确回答您的问题,我建议我们退后一步,探讨一下编程语言中相等意味着什么的问题。至少存在三种不同的可能性,它们以各种语言使用:

  • 引用相等:意味着如果a和b引用相同的对象,则a = b为true。即使a和b引用了不同的对象,即使a和b的所有属性都相同,也不是正确的。
  • 浅相等:意味着如果a和b引用的对象的所有属性都相同,则a = b为true。通过对代表两个对象的内存空间进行按位比较,可以轻松实现浅相等。请注意,引用相等意味着浅层相等
  • 深度相等:表示如果a和b中的每个属性相同或高度相等,则a = b为真。请注意,引用相等和浅层相等都暗示着深度相等。从这个意义上说,深层平等是最弱的平等形式,而参照平等是最强的平等形式。

经常使用这三种类型的相等性,因为它们易于实现:编译器可以轻松生成所有三种相等性检查(在深度相等的情况下,如果结构要被比较具有循环引用)。但是还有另一个问题:这些都不适合。

在非平凡的系统中,对象的相等性通常定义为深度和引用相等性之间的某种关系。为了检查我们是否希望在特定上下文中将两个对象视为相等,我们可能需要通过比较它们在内存中的位置来比较某些属性,并通过深度相等来比较其他属性,而某些属性可能被允许完全不同。我们真正想要的是一种“第四种平等”,这是一种非常好的,在文献中经常被称为语义平等。在我们的领域中,如果事物相等,则事物相等。=)

所以我们可以回到您的问题:

我根本没有想到的默认设置是否有一些主要好处,或者默认行为应该是逻辑相等,并且如果该类不存在逻辑相等,则默认返回引用相等是否合理?

用任何语言写“ a == b”是什么意思?理想情况下,它应该始终是相同的:语义平等。但这是不可能的。

主要考虑因素之一是,至少对于简单的类型(如数字),我们期望分配相同值后两个变量相等。见下文:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

在这种情况下,我们期望两个语句中的“ a等于b”。其他任何事情都会发疯。大多数(如果不是全部)语言都遵循此约定。因此,通过简单的类型(又称值),我们知道如何实现语义相等。对于对象,这可能是完全不同的东西。见下文:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

我们希望第一个“如果”始终为真。但是您对第二个“ if”有什么期望?真的要看 'DoSomething'可以改变a和b的(语义)相等吗?

语义相等的问题在于,它不能由编译器自动为对象生成,也不能从赋值中显而易见。必须为用户提供定义语义相等性的机制。在面向对象的语言中,该机制是一个继承的方法:equals。阅读一段OO代码,我们不希望某个方法在所有类中都具有完全相同的实现。我们习惯于继承和重载。

但是,对于运算符,我们期望相同的行为。当您看到'a == b'时,您应该在所有情况下都具有相同的相等类型(来自上面的4)。因此,为了保持一致性,语言设计人员对所有类型都使用了引用相等性。它不应该取决于程序员是否重写了方法。

PS:语言Dee与Java和C#略有不同:equals运算符表示简单类型的浅层相等,而用户定义的类的语义相等(实现=操作的责任在于用户-未提供默认值)。因为对于简单类型,浅相等始终是语义相等,所以语言是一致的。但是,它付出的代价是,对于用户定义的类型,默认情况下未定义equals运算符。您必须实现它。而且,有时候,这很无聊。


2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Java的语言设计师将引用相等用于对象,将语义相等用于基元。对我来说,这不是一个正确的决定,或者比==为对象的语义相等而允许重载,该决定更“一致”,这对我来说并不明显。
Charles Salvia

他们也将“参考相等的等价物”用于基元。当您使用“ int i = 3”时,该数字没有指针,因此您不能使用引用。使用字符串(一种“某种”原始类型),它更加明显:您必须使用“ .intern()”或直接赋值(字符串s =“ abc”)才能使用==(引用相等)。
Hbas

1
PS:另一方面,C#与它的字符串不一致。在这种情况下,恕我直言,那就更好了。
Hbas

@CharlesSalvia:在Java中,如果ab类型相同,则表达式a==b测试是否ab持有相同的东西。如果其中一个持有对对象#291的引用,而另一个持有对对象#572的引用,则它们不持有同一对象。对象#291和#572 的内容可以相等,但是变量本身具有不同的内容。
超级猫

2
@CharlesSalvia它的设计方式使您可以看到a == b并知道它的作用。同样,您可以看到a.equals(b)并假定一个重载equals。如果a == b调用a.equals(b)(如果已实现),是按引用还是按内容进行比较?不记得了 您必须检查A类。如果您甚至不确定所调用的内容,那么代码的读取速度将不再那么快。好像允许使用具有相同签名的方法一样,所调用的方法取决于当前作用域。这样的程序将无法阅读。
尼尔

0

我试图思考为什么这两种语言都采用这种方式而不是颠倒它,而是使==成为逻辑相等并使用.ReferenceEquals()进行引用相等。

因为后一种方法会造成混乱。考虑:

if (null.ReferenceEquals(null)) System.out.println("ok");

该代码应该打印"ok"还是应该抛出NullPointerException


-2

对于Java和C#,好处在于它们是面向对象的。

性能的角度来看,编写更容易的代码也应该更快:由于OOP打算用不同的对象表示逻辑上不同的元素,因此考虑到对象可能变得很大,检查引用的相等性会更快。

逻辑的角度来看,一个对象与另一个对象的相等性不必与比较对象的相等性一样明显(例如,null == null在逻辑上是如何解释的?这可能因情况而异)。

我认为这归结为您的观察,即“您始终希望逻辑相等而不是引用相等”。语言设计者之间的共识可能是相反的。我个人很难对此进行评估,因为我缺乏广泛的编程经验。粗略地说,我在优化算法中更多地使用了引用相等性,而在处理数据集时则更多地使用了逻辑相等性。


7
引用相等与面向对象无关。实际上,情况恰恰相反:面向对象的基本属性之一是具有相同行为的对象是无法区分的。一个对象必须能够模拟另一个对象。(毕竟,OO是为模拟而发明的!)引用相等允许您区分具有相同行为的两个不同对象,它使您可以区分模拟对象和真实对象。因此,引用相等破坏了面向对象。OO程序不得使用引用相等。
约尔格W¯¯米塔格

@JörgWMittag:要正确地执行面向对象的程序,需要有一种方法来询问对象X的状态是否等于Y的状态(潜在的瞬态条件),还需要询问对象X是否等效于Y的方法。 [仅当保证其状态永远等于Y时,X才等于Y。为等效性和状态相等性使用单独的虚拟方法会很好,但是对于许多类型,引用不平等将意味着非等效性,因此没有理由花时间在虚拟方法分派上进行证明。
超级猫

-3

.equals()比较变量的内容。而不是==通过对象的内容来比较对象...

使用对象更准确地使用 .equals()


3
您的假设是不正确的。.equals()可以完成.equals()的编码工作。通常是按内容,但不一定如此。同样,使用.equals()并不是更准确。这仅取决于您要完成的工作。
拉链
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.