今回は 等価性 だ。
等価という考え方は非常に大事だから
是非マスターしようぜ!
等価
等価を考える上で、大事なことが2つあるんだ。
等価性 と 同一性 だ。
それぞれを説明してみるよ。
等価性
同値性 と呼ぶこともあるよ。
あるオブジェクトA、Bがあったとき、2つが全く同じ値を持つことを等価 という。
等価性を判定するのは簡単。==
を使えば良い。
ちなみに!=
は、 否定 の判定になるぞ。
では、動かしてみようぜ!
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
domain1
とdomain2
は同じフィールド値を持つので、等価ということだ。
判定には何を使っているのか
気づいてくれてたかもしれないけど
勝手にフィールドで判定してくれるわけではないんだ。
判定にはequals
メソッドを使うんだ。
==
を使用すると、equals
に判定を委譲するんだ。
そのため、equals
の実装が必要だ。
なのでクラスDomain
に以下のメソッドを追加している。
equals
hashCode
canEqual
ここはどう実装したのかって?
EclipseのScala-IDEプラグインを使って生成している。
詳細に入るとは混みいってくるので、今回はゴメンな。
同一性
次に同一性を見てみよう。
__参照の等価性__の意味。
あるオブジェクトA、Bがあったとき、2つが同じオブジェクトであれば同一 という。
同一性を判定するには、eq
を使用する。
ちなみにne
は、 否定 の判定になるぞ。
こっちも、動かしてみようぜ!
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
domain1
とdomain3
は単なる等価だから
eq
の結果はtrue
になっていないね!
Javaとの違い
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
が実装されるか見てみよう!
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
がどうなるるか気になったので次のソースを書いてみよ。
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 だ!
まとめ
今回は、等価性だったけどどうだった?
実際にアプリケーション作ると等価性に救われることが
良くあるぞ!
今回も
体で感じてくれたかな?