Help us understand the problem. What is going on with this article?

第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 だ!

まとめ

今回は、等価性だったけどどうだった?
実際にアプリケーション作ると等価性に救われることが
良くあるぞ!

今回も
体で感じてくれたかな?

f81@github
Fringe81のエンジニアが頑張って執筆ちゅうです! Scala の修行を始めました。 みなさま、温かい目で見守ってください。
fringe81
Fringeは、最新のテクノロジーとプロフェッショナルによるサービスにより、社会課題に仮説を立てて市場に広げていくことで、数十年という長期的なスパンで価値を生み出し続け、より良い世界を創る集団です。 既存の領域に限らず、時流を読み、仮説を生み出し、テクノロジーの力で優れたサービスを生み出し続けます。
https://www.fringe81.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away