🔍
Javaとの比較で学ぶScalaの等価比較:なぜ同じ値でもfalseにならないのか
1. まずは Java の “あるある” を確認(背景)
Java では:
- プリミティブ同士の == … 値を比較(例: int)
- 参照型同士の == … 参照(アドレス)を比較(例: Integer, String, 自作クラス)
- auto-boxing が入ると 同じ数値でも異なる Integer インスタンスになり、== は false に
たとえば Java でこんなことが起きます(雰囲気の説明):
Integer a = new Integer(421);
Integer b = new Integer(421);
System.out.println(a == b); // false 参照が違うから
System.out.println(a.equals(b)); // true 値は同じ
2. Scala の “普遍的等価(universal equality)”
Scala の == は常に “値の等価” を見ます。
内部的には null セーフな equals 呼び出しだと思ってください。
2-1. 数値なら素直に true
scala> 421 == 421
val res0: Boolean = true
2-2. 型が違っても数値なら中身が同じなら true
scala> 1 == 1L
val res1: Boolean = true
scala> 3.0 == 3
val res2: Boolean = true
2-3. 文字列も「値」で判定
scala> val x = "abcd".substring(2) // "cd" (新しいインスタンス)
val x: String = cd
scala> val y = "abcd".substring(2) // こちらも "cd" (別インスタンス)
val y: String = cd
scala> x == y
val res3: Boolean = true
別インスタンスかどうか(参照の同一性)は無視して、中身が同じなら true です。
3. 「参照が同じかどうか」を見たいときは eq / ne
Scala では参照の同一性を確かめたい場合、演算子を使います:
- a eq b … 同一インスタンスなら true
- a ne b … 同一インスタンスでないなら true
scala> val s1 = new String("abc")
val s1: String = abc
scala> val s2 = new String("abc")
val s2: String = abc
scala> s1 == s2 // 値は同じ
val res4: Boolean = true
scala> s1 eq s2 // 参照は別
val res5: Boolean = false
scala> s1 ne s2
val res6: Boolean = true
4. null にも優しい(NPE を避けやすい)
Scala の == は null セーフです。どちらかが null でも NPE になりません。
scala> val a: String = null
val a: String = null
scala> val b: String = "x"
val b: String = x
scala> a == b // NPE にならない
val res7: Boolean = false
5. (おまけ)数値でも “落とし穴仕様” は尊重される
浮動小数点の NaN は自分自身と等しくない(IEEE 754)という仕様は Scala でも同じです:
scala> val d = Double.NaN
val d: Double = NaN
scala> d == d
val res8: Boolean = false
この辺りは 数学的な仕様由来なので、Scala でも合わせています。
6. まとめ:迷いにくい Scala の等価
- Scala の == は常に値の等価(equals を null セーフに呼ぶ)
→ Java の auto-boxing 由来の “参照比較になってしまう” 事故を回避 - 参照の同一を見たいなら eq / ne を使う(意図が明確)
- null にも優しくて NPE を避けやすい
付録:一気におさらい(コピペで試せる)
// 1) 値の等価(数値)
421 == 421 // true
1 == 1L // true
3.0 == 3 // true
// 2) 文字列:中身が同じなら true
val x = "abcd".substring(2) // "cd"(別インスタンス)
val y = "abcd".substring(2) // "cd"
x == y // true
// 3) 参照の同一
val s1 = new String("abc")
val s2 = new String("abc")
s1 == s2 // true(値)
s1 eq s2 // false(参照)
s1 ne s2 // true
// 4) null セーフ
val a: String = null
val b: String = "x"
a == b // false(NPEにならない)
Discussion