scalaの等価性

2019.8.24 12:00

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. */
}
  1. Any#equalsをオーバーライドする

    • 引数の型はクラスの型ではなくAnyになる
  2. hashCodeは常にequalsで使っている値をすべて使って常に同じ計算結果となるようにする

    • ##で簡単にハッシュ値を作れる
  3. ミュータブルなプロパティをequalsに使用してはいけない
  4. 下記条件を満たすようにする

    • 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
  5. 冪等である

継承関係にあるクラスでのポイント

継承関係にあるクラスでの等価性は、基本的に厳格にしたほうが良い場合が多い。

例えば、下記のようなTextAttributedTextのクラスでは、比較結果が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

scala

Copyright 2020 tkzwhr's tech notes. All Rights Reserved. Built with Gatsby.