19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

まとめ

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

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

19
17
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?