Posted at

第16章:Scalaの等価性

More than 5 years have passed since last update.

今回は 等価性 だ。

等価という考え方は非常に大事だから

是非マスターしようぜ!


等価

等価を考える上で、大事なことが2つあるんだ。

等価性同一性 だ。

それぞれを説明してみるよ。


等価性

同値性 と呼ぶこともあるよ。

あるオブジェクトA、Bがあったとき、2つが全く同じ値を持つことを等価 という。

等価性を判定するのは簡単。==を使えば良い。

ちなみに!=は、 否定 の判定になるぞ。

では、動かしてみようぜ!


Equivalence.scala

object Equivalence {

def main(args: Array[String]): Unit = {
val domain1 = new Domain1(1, "富樫")
val domain2 = new Domain1(1, "富樫")

println(domain1 == domain2)
println(domain1 != domain2)

val domain3 = new Domain1(2, "虎丸")

println("==:", domain1 == domain3)
println("!=:", domain1 != domain3)
}

}

class Domain1(val id: Long, val name: String) {
def canEqual(other: Any) = {
other.isInstanceOf[Domain1]
}

override def equals(other: Any) = {
other match {
case that: Domain1 =>
that.canEqual(Domain1.this) && id == that.id && name == that.name
case _ => false
}
}

override def hashCode() = {
val prime = 41
prime * (prime + id.hashCode) + name.hashCode
}
}


これが動かした結果だ!

$ scala Equivalence

true // domain1 == domain2
false // domain1 != domain2
false // domain1 == domain3
true // domain1 != domain3

domain1domain2は同じフィールド値を持つので、等価ということだ。


判定には何を使っているのか

気づいてくれてたかもしれないけど

勝手にフィールドで判定してくれるわけではないんだ。

判定にはequalsメソッドを使うんだ。

==を使用すると、equalsに判定を委譲するんだ。

そのため、equalsの実装が必要だ。

なのでクラスDomainに以下のメソッドを追加している。


  • equals

  • hashCode

  • canEqual

ここはどう実装したのかって?


EclipseのScala-IDEプラグインを使って生成している。


詳細に入るとは混みいってくるので、今回はゴメンな。


同一性

次に同一性を見てみよう。

参照の等価性の意味。

あるオブジェクトA、Bがあったとき、2つが同じオブジェクトであれば同一 という。

同一性を判定するには、eqを使用する。

ちなみにneは、 否定 の判定になるぞ。

こっちも、動かしてみようぜ!


Identity.scala

object Identity {

def main(args: Array[String]): Unit = {
val domain1 = new Domain2(1, "富樫")
val domain2 = domain1

println(domain1 eq domain2)
println(domain1 ne domain2)

val domain3 = new Domain2(1, "富樫")

println(domain1 eq domain3)
println(domain1 ne domain3)
}

}

class Domain2(val id: Long, val name: String) {
def canEqual(other: Any) = {
other.isInstanceOf[Domain2]
}

override def equals(other: Any) = {
other match {
case that: Domain2 =>
that.canEqual(Domain2.this) && id == that.id && name == that.name
case _ => false
}
}

override def hashCode() = {
val prime = 41
prime * (prime + id.hashCode) + name.hashCode
}
}


これが結果だ!

$ scala Identity

true // domain1 eq domain2
false // domain1 ne domain2
false // domain1 eq domain3
true // domain1 ne domain3

domain1domain3は単なる等価だから

eqの結果はtrueになっていないね!


Javaとの違い

Javaとは記述が違うから、一応書いておくね。


Equals.java

public class Equals {

public static void main(String[] args) {
final String a = new String("test");
final String b = new String("test");

System.out.println(a == b);
System.out.println(a.equals(b));
}
}


実行した結果がこれだ。

Javaの動かし方は、記述しないからね。

false  // a == b

true // a.equals(b)

Javaでは以下で表すんだ。


  • 等価性


    • equals



  • 同一性


    • ==




ケースクラス

最後に、ケースクラスを簡単に紹介だ!

ケースクラスを使うと、 equalsとかの実装が自動で定義されるようだ。

何が定義されるかは、まだ把握できていないんだ。。。

一先ずequalsが実装されるか見てみよう!


CaseClass.scala

object CaseClass {

def main(args: Array[String]): Unit = {
val domain1 = new Domain3(1, "富樫")
val domain2 = new Domain3(1, "富樫")

println("==:", domain1 == domain2)
println("!=:", domain1 != domain2)

val domain3 = new Domain3(2, "虎丸")

println("==:", domain1 == domain3)
println("!=:", domain1 != domain3)

}

case class Domain3(val id: Long, val name: String)
}


実行してみよう!

scala CaseClass

true // domain1 == domain2
false // domain1 != domain2
false // domain1 == domain3
true // domain1 != domain3

Equivalence.scalaの結果と同じになったね!

明示的な実装をせず、ケースクラスにもしない場合、

equalsがどうなるるか気になったので次のソースを書いてみよ。


NoneCaseClass.scala

object NoneCaseClass {

def main(args: Array[String]): Unit = {
val domain1 = new Domain4(1, "富樫")
val domain2 = domain1

println("==:", domain1 == domain2)
println("!=:", domain1 != domain2)

val domain3 = new Domain4(2, "虎丸")

println("==:", domain1 == domain3)
println("!=:", domain1 != domain3)

}

class Domain4(val id: Long, val name: String)
}


動かすと次のようになる。

$ scala NoneCaseClass

true // domain1 == domain2
false // domain1 != domain2
false // domain1 == domain3
true // domain1 != domain3

これは、同一性の実装がしてあると推測できるね。

実際はどうなっているかは、Jadを使って見てみてね!

ケースクラスについてもう一つ。

そもそもequalsの実装は、少なくとも以下の2パターンはあると思う。


  • 全フィールドで見ないといけない

  • 識別子となるフィールドのみを見る

なので、なんでもかんでもケースクラス使えばOK!

NG だ!


まとめ

今回は、等価性だったけどどうだった?

実際にアプリケーション作ると等価性に救われることが

良くあるぞ!

今回も

体で感じてくれたかな?