LoginSignup
6

More than 5 years have passed since last update.

Scalaの等値比較演算子は何故nullチェックが出来るのか

Last updated at Posted at 2016-02-16

Scalaの等値比較演算子は何故nullチェックが出来るのか

はじめに

NullCheck.scala
val a:AnyRef = null
if (a == null) {
    println("a is null")
} else {
    println("a is not null")
}

Scalaにおいて、==は等値比較を実施するものですが、等値比較演算子でnullチェックを行えるのは何故でしょう?

というのは、上記をJavaに翻訳してみると

NullCheck.java
Object a = null;
if (a.equals(null)) {
// -> Exception in thread "main" java.lang.NullPointerException
    System.out.println("a is null");
} else {
    System.out.println("a is not null");
}

のようになるため、a.equals(null)を実行した際にNullPointerExceptinが発生します。Scalaは何故エラーにならないのでしょう?

調査結果

結論から言うと、==の使用方法により3パターンのバイトコードが作成され、上手く動いてくれます。

パターン1 ==の右辺がnullの場合

元のScalaのコードを機械語翻訳して、比較処理をしている場所を抜粋します。

     3  astore_2 [a]
     4  aload_2 [a]
     5  ifnonnull 19

if (a == null)ifnonnullに翻訳されてます。==の右辺がnullの時、ifnotnullという専用のコードを実行するようになってます。頭良い。

パターン2 右辺がnullと判断できない場合

ちなみに右辺がコンパイル時点でnullと判断出来ない場合、

NullCheck.scala
val a:AnyRef = null
val theNull:AnyRef = null
if (a == theNull) {
    println("a is null")
} else {
    println("a is not null")
}

     8  aload_2 [a]
     9  aload_3 [theNull]
    10  invokestatic scala.runtime.BoxesRunTime.equals(java.lang.Object, java.lang.Object) : boolean [20]
    13  ifeq 27

a == theNullscala.runtime.BoxesRunTime.equals(java.lang.Object, java.lang.Object)の呼び出しに変換されます。

そこでこのメソッドは何かと言うと、

BoxesRuntime.java
    public static boolean equals(Object x, Object y) {
        if (x == y) return true;
        return equals2(x, y);
    }

    public static boolean equals2(Object x, Object y) {
        if (x instanceof java.lang.Number)
            return equalsNumObject((java.lang.Number)x, y);
        if (x instanceof java.lang.Character)
            return equalsCharObject((java.lang.Character)x, y);
        if (x == null)
            return y == null;

        return x.equals(y);
    }

となっています。一度nullチェックをした後に、equalsを呼び出して等値比較を呼び出しているようです。頭良い。

パターン3 ==をオーバーロードした場合

ここで疑問に思うのが==をオーバーロードしたケースです。こうなるとBoxesRunTime.equalsが使えません。

A.scala
class A {
    def ==(that: A) = true
}
NullCheck.scala
val a:A = null
if (a == null) {
// -> Exception in thread "main" java.lang.NullPointerException
    println("a is null")
} else {
    println("a is not null")
}

比較した瞬間に例外が発生します。

どのようなコードが吐かれているかと言うと。

     7  aconst_null
     8  invokevirtual nullcheck.A.$eq$eq(nullcheck.A) : boolean [20]
    11  ifeq 25

BoxesRunTime.equalsを経由せずにA#==を呼び出すため、例外が発生します。
そのため、==をオーバーロードしたオブジェクトは簡単にはnullチェックが出来なくなりそうです。

結び

普通はOptionを使います。

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
6