Scalaの等値比較演算子は何故nullチェックが出来るのか
はじめに
val a:AnyRef = null
if (a == null) {
println("a is null")
} else {
println("a is not null")
}
Scalaにおいて、==
は等値比較を実施するものですが、等値比較演算子でnullチェックを行えるのは何故でしょう?
というのは、上記を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と判断出来ない場合、
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 == theNull
はscala.runtime.BoxesRunTime.equals(java.lang.Object, java.lang.Object)
の呼び出しに変換されます。
そこでこのメソッドは何かと言うと、
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が使えません。
class A {
def ==(that: A) = true
}
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を使います。