🔍

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