Scalaの演算子はメソッド、ではなぜ==がぬるぽにならないのか


Scalaの本やblogを読んでいるとScalaの演算子はメソッドである、という説明が出てきます。

今日はその話です。

+演算子をメソッド呼び出しで

実際にやってみると・・・

scala> val a = 10
a: Int = 10

scala> val b = 20
b: Int = 20

scala> val res1 = a + b
res1: Int = 30

scala> val res2 = a.+(b)
res2: Int = 30

a.+(b)が成功します。

==演算子をメソッド呼び出しで・・・ぬるぽにならない

ここで一つ疑問が生まれます。

「==もメソッド呼び出しであるのならば、==のCaller側がnullだったらぬるぽになるんじゃないの?」です。

scala> val a = null
a: Null = null

scala> val b = 20
b: Int = 20

scala> val res = a.==(b)
<console>:9: warning: comparing values of types Null and Int using `==' will always yield false
       val res = a.==(b)
                     ^
res: Boolean = false

(warningは出ていますが、それは一旦置いておいて)NullPointerExceptionが発生していません。Javaならaがnullならぬるぽになりますね。Scalaでもaがnullの状態で他のメソッドを呼び出すとぬるぽになります。

例えば

scala> val nullString: String = null
nullString: String = null

scala> nullString.length
java.lang.NullPointerException
  ... 33 elided

なぜ==だけ・・・と思ってscalajp/public - Gitterで質問したら@xuwei_kさんが教えてくれました。

どうやらScalaコンパイラの仕様みたいです。nullチェックのコードを自動で仕込むから、だそうです。

逆コンパイルで確認

以下のコードをscalacでコンパイルして、JavaDecompilerで逆コンパイルし、確認してみます。

元コード

object Main extends App {
  val a = null
  val b = 20
  if(a == b) println("equal!!!") else println("not equal...")
}

実行結果

[tsukaby@tsukamac tmp]% scala Main
not equal...

逆コンパイル結果の一部

  public final void delayedEndpoint$Main$1()
  {
    this.a = null;
    this.b = 20;
    a(); localInteger = BoxesRunTime.boxToInteger(b());
     tmp25_16 = null; if (tmp25_16 == null) { tmp25_16; if (localInteger == null) break label44; tmpTernaryOp = tmp25_16; break label55;
    }
  }

分かりづらいですが、勝手にifによるnullチェックが入っています。

という訳で、ぬるぽにならないことが分かりました。