物件相等性


在Scala中,如果要比較兩個物件的實質相等性,可以使用==或!=,例如:
val s1 = new String("Java")
val s2 = new String("Java")
println(s1 == s2) // 顯示 true
println(s1 eq s2) // 顯示 false

雖然兩個物件是新建構出來的,s1與s2是參考到不同物件,但在Scala中使用==是比較兩個字串的實質字元序列,所以結果會是true,在Scala中若要測試兩個參考是否為同一物件,則可以使用eq或ne方法。

==與!=方法有兩個版本,一個版本是定義在 scala.Any 中,Any是Scala中所有類別的最頂層父類別,其中==與!=的方法定義為:
final def ==(arg0 : Any) : Boolean
final def !=(arg0 : Any) : Boolean

在API文件說明中表示,==的作用等於equals()的作用:
def equals(arg0 : Any) : Boolean

equals()的作用是測試兩個args0的作用與this是否參考同一物件。而!=等於==的反相結果,也就是o != arg0等於!(o == (arg0))。

事實上,當你在Scala中定義一個類別時沒有明確指定父類別,則會繼承 scala.AnyRef(相當於Java的java.lang.Object),這是Any的直接子類別。AnyRef定義了==與!=,其版本為:
final def ==(arg0 : AnyRef) : Boolean 
final def !=(arg0 : AnyRef) : Boolean

!=等於==的反相結果,而==的定義內容相當於:
final def == (arg0 : Any): Boolean =
    if (this eq null) arg0 eq null else this.equals(arg0)

eq與ne方法,是定義在AnyRef中(在Scala中,像1這樣的物件是 scala.AnyVal 的實例,AnyVal是Any的直接子類別,所以AnyVal沒有eq與ne方法,AnyVal物件只要值相同,一定是同一個物件實例,也就是 1 equals 1 結果一定是true):
final def eq(arg0 : AnyRef) : Boolean 
final def ne(arg0 : AnyRef) : Boolean 

ne為eq的反相結果,而eq主要在測試目前物件與arg0所參考的物件是否為同一物件。

無論是Any中的==、!=或AnyRef中的==、!=、eq、ne方法,都被宣告為final,你沒辦法在子類別中重新定義(AnyVal被宣告為final,你沒辦法繼承),別以為以下是重新定義==方法:
class Point(val x: Int, val y: Int) {
def ==(that: Point) = this.x == that.x && this.y == that.y
}

val p1 = new Point(1, 1)
val p2 = new Point(1, 1)
println(p1 == p2) // 顯示 true

看來好像是對的,但是如果你這麼測試:
val p1: AnyRef = new Point(1, 1)
val p2 = new Point(1, 1)
println(p1 == p2) // 顯示 false

事實上是,你沒有重新定義Any或AnyRef的==方法,你是定義了一個新的==方法,而直接繼承了Any與AnyRef的==方法,所以之前顯示true是因為方法被重載了,而你使用的是接受Point參數的==版本。

因為Any與AnyRef的==、!=都被宣告為final,你不能重新定義==、!=方法來定義自己的物件相等性。依以上的說明,無論是哪個版本,!=一定是==的反相結果,而==都會呼叫equals()方法,所以結論是,要定義物件相等性,請重新定義equals()方法(跟Java相同)。

如何正確定義equals()需要作些討論,如果你熟悉Java,可以先看看 Java 物件相等性 中的說明,如果你想要知道如何以Scala語法正確定義equals()方法,則可以看看 重新定義 equals() 方法