Scala、Java和相等性

7
val filesHere = (new java.io.File(".")).listFiles
val filesHere2 = (new java.io.File(".")).listFiles

scala> filesHere == filesHere2
res0: Boolean = false

这很反直觉。我本来期望filesHere和filesHere2是相等的。

这显然是由于Java和Scala之间的语义不匹配造成的,例如数组或(文件)相等性方面。显然,我在这里缺少了某些东西!

4个回答

17
如果我掌管这个世界,我会因为Scala的eq方法名称与equals和==极为相似而弃用它。 而英语确实有一个表达标识(identity)而不是相等(equality)的单词:我会简单地称之为is。
类似地,我会用isnt替换Scala的ne(这是一个可怕的名称,因为它既是缩写又难以理解)。
在我看来,这些方法实际上可以添加到AnyRef中,并弃用旧方法,即使在这个阶段。

3
好主意,但是像 obj is String 这样的类型检查中,“is”是含糊不清的,在其他语言中很流行,因此容易引起混淆。 - tuxSlayer

8
Java数组的equals()方法使用引用相等性而不是更复杂的内容,而Scala的==只是Java的equals()方法。

在Scala中,==和equals()用于值相等性,而eq用于判断两个对象是否具有引用相等性。 - James Black
1
@James 是的,但在这种情况下,equals() 的实现仅用于检查引用相等性。因此在这种特定情况下,(Scala的==) == (Java的==) :) - Tom Crockett
这里有一个有趣的线程:http://scala-programming-language.1934581.n4.nabble.com/scala-Array-equality-td2001726.html但是,我还是很困惑。如果Scala ==(或“equals”)实际上调用Java的equals()方法(似乎是这种情况),那么这个例子在某种程度上破坏了Scala语言关于相等性的设计选择。 作为Scala程序员,我必须提醒Java中相等性的语义,并小心处理所有棘手的问题。该死! - acherm
你并不是唯一被这个困惑的人...许多不幸的Scala新手都会被它绊倒。你至少明白发生了什么吗? - Tom Crockett
1
顺便说一下,很多Java开发者都会说:“除非你必须使用遗留API,否则不要使用数组,而是使用集合。” 集合具有强大的“equals()”语义。 - Thomas Dufour

8

比较结果与预期不符,因为此 Java API 返回一个数组。

Scala 的数组和 Java 数组在底层是相同的,尽管 Scala 的数组看起来像是一个类,但实际上它只是一个 java.io.File[](在这个例子中)。

这就是为什么无法重写等号检查的原因。Scala 必须使用 Java 的语义进行比较。

考虑以下示例:

val filesHere = (new java.io.File(".")).listFiles.toList
val filesHere2 = (new java.io.File(".")).listFiles.toList

这将按预期工作。


@FUD 是的,因为它是一个数组。但是如果你调用.toList -- 它就会开始工作。这是因为它将变成Scala世界中的一个类,在Scala中定义类使用更好的equals - VasiliNovikov

4
您可能需要阅读这里:http://ofps.oreilly.com/titles/9780596155957/AdvancedObjectOrientedProgramming.html#EqualityOfObjects,但是看起来如果您这样做:filesHere.sameElements(filesHere2) 就应该是true。
此函数的javadoc在这里:http://www.scala-lang.org/api/2.6.0/scala/IterableProxy.html#sameElements%28Iterable%5BB%5D%29 更新:第一个链接中有几个片段可能会有所帮助:
在Java、C++和C#中,==运算符测试引用而不是值相等。相反,Ruby的==运算符测试值相等。无论你使用哪种语言,请记住,在Scala中,==测试值相等。
关于列表上的==不能按预期工作的问题:
虽然这似乎是一种不一致性,但是鼓励明确测试两个可变数据结构的相等性是语言设计者的保守方法。从长远来看,这应该可以避免在条件语句中出现意外结果。
更新2:根据Raphael的评论,我查看了规范,并且最近的更新日期是两天前,我在这里看到了这些:http://www.scala-lang.org/files/archive/nightly/pdfs/ScalaReference.pdf
Method equals: (Any)Boolean is structural equality, where two instances
are equal if they both belong to the case class in question and they have equal
(with respect to equals) constructor arguments.

class AnyRef extends Any {
    def equals(that: Any): Boolean = this eq that
    final def eq(that: AnyRef): Boolean = . . . // reference equality

所以,在Scala 2.10.2中,定义似乎没有改变,因为规范似乎是一致的。如果行为不同,那么如果您编写一个单元测试,它应该被发送给Scala作为错误。


@Raphael - 根据这两个链接,似乎它并没有改变:http://programmers.stackexchange.com/questions/193638/why-didnt-operator-string-value-comparison-make-it-to-java,http://blog.scala4java.com/2013/05/scala-equality-in-30-seconds.html - James Black
@JamesBlack 我正在查看比你链接的[programmers.SE]更为最新的API of 2.10.2。至少,我将“equality”解读为“相同的对象”,而“equivalence”则是“相同的值”。我和朋友进行了一些小型测试,但结果令人困惑(覆盖equals似乎会影响对equals==的调用),所以我想问一下您的意见。您认为我应该提出一个正式的问题吗?(第二个链接对我无效。) - Raphael
@Raphael - 我刚刚更新了我的答案,但根据规范,我的答案仍然是一致的。 - James Black
@JamesBlack 谢谢。我仍然不明白 == 如何适用,以及如何覆盖 equals 即使它们是不同的规范。案例类是否与普通类有所不同? - Raphael
1
@Raphael - 如果您查看http://www.scala-lang.org/old/node/258,您将看到情况类中的equals函数被重新定义为自动比较结构(值)。实际上,我不知道equals如何同时更改两者,但您可能会发现这个答案有帮助:https://dev59.com/pWsz5IYBdhLWcg3wwKqh#7681243 - James Black
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接