ScalaとJavaの違い
両言語の取扱いの違いについて下記にまとめる。
Scala | Java | |
---|---|---|
値の比較 | == |
== |
オブジェクトの比較(値等価) | == |
equals |
オブジェクトの比較(参照等価) | eq |
== |
オブジェクトの比較(ユーザ定義等価) | == (override equals ) |
override equals |
等価性実装のポイント
class A(val value: String) { var mutatedValue: String /* 3. */ override def equals(x: Any /* 1. */) = x match { case xx: A => value == xx.value case _ => false } override def hashCode = value.## /* 2. */ }
Any#equals
をオーバーライドする- 引数の型はクラスの型ではなく
Any
になる
- 引数の型はクラスの型ではなく
hashCode
は常にequals
で使っている値をすべて使って常に同じ計算結果となるようにする##
で簡単にハッシュ値を作れる
- ミュータブルなプロパティを
equals
に使用してはいけない - 下記条件を満たすようにする
x.equals(x) == true // x is not null
x.equals(y) == y.equals(x) // x, y is not null
when x.equals(y) == y.equals(z), x.equals(z) == true // x, y, z is not null
x.equals(null) == false // x is not null
- 冪等である
継承関係にあるクラスでのポイント
継承関係にあるクラスでの等価性は、基本的に厳格にしたほうが良い場合が多い。
例えば、下記のようなText
とAttributedText
のクラスでは、比較結果がtrue
になるパターンをあれこれ考えるより、相互に比較結果がfalse
になるように実装したほうが比較時の違和感が少ない。
class Text(val value: String) { override def equals(x: Any) = x match { case xx: Text => canEqual(x) && value == xx.value case _ => false } override def hashCode = value.## def canEqual(x: Any) = x.isInstanceOf[Text] // 一部の等価性を比較したい場合は`==`ではなく独自にメソッドを実装したほうがわかりやすい def equalsText(x: Text) = value == x.value } class AttributedText(override val value: String, val attributes: Map[String, Any]) extends Text(value) { override def equals(x: Any) = x match { case xx: AttributedText => canEqual(x) && super.equals(x) && attributes == xx.attributes case _ => false } override def hashCode = (value, attributes).## override def canEqual(x: Any) = x.isInstanceOf[AttributedText] }
上記のようにcanEqual
を実装し、インスタンス判定を条件に加えることで実現できる。
また、こうしておくと、無名サブクラスでも正しく判定されるようになる。
val text = new Text("test") val anotherText = new Text("sample") { override val value: String = "test" } println(text == anotherText) // ==> true