Android Studio に warning を出されるようになった
Kotlin で以下のような構文を書くと Android Studio に warning を出されるようになった:
if (value?.isEmpty() ?: false) // Equality check can be used instead of elvis
if (value?.isEmpty() == true) // これなら OK
この場合は elvis 演算子 ?:
ではなく ==
で代用できるというわけである。
何故私が上の elvis 演算子 を使った構文を使ってしまっていたかというと、紛れもなく Java では false
は基本型 boolean
であるという事実が頭にあったからであり Java の概念に引きずられて if (value?.isEmpty() == true)
の方の例が些か直感的でないように感じてしまったからだ。
以下どういうことなのか考察する。
Java では
Java では以下のような比較は syntax error となる:
if (null == false) // syntax error. false はオブジェクトではない!!
if (null == Boolean.FALSE) // これなら OK
Java は歴史的な経緯にて全てのフィールドやメソッドがオブジェクトというわけではなく基本型 (プリミティブ型) とオブジェクト型に分類される。
そもそも基本型が null
になることはなく基本型に対する ==
比較は基本型且つ型が一致していないと行えない。
基本型はオブジェクトではないので基本型に対するメソッド呼び出しを行うことはできない。
メソッド呼び出しを行いたい場合は対応するラッパー型 (オブジェクト) に変換した上でメソッド呼び出しを行う。
上記の例の場合 boolean
のラッパー型は Boolean
なので false
に対しメソッド呼び出しを行いたいならば Boolean.FALSE
を使えば良い。
Boolean.TRUE
と Boolean.FALSE
はそれぞれ java.lang.Boolean
クラスに対するインスタンスである。
一方 Java における ==
演算子の役割はオブジェクトの参照が等しいことの比較なので以下のような null
との比較は false
となる:
System.out.println(null == Boolean.TRUE); // false
System.out.println(null == Boolean.FALSE); // false
一方 Boolean.equals()
の実装も「比較対象が Boolean
クラスでない場合 (null
も含む) 常に false
となる」となっているので以下も合点がいく:
System.out.println(Boolean.TRUE.equals(null)); // false
System.out.println(Boolean.FALSE.equals(null)); // false
まとめると Java では null
は true
でも false
でもない第三の値である。
一方以下のように null
と null
の同値比較は true
となる事に注意する:
System.out.println(null == null); // true
Kotlin に話を戻す
話を戻すが Kotlin では以下だとコンパイルが通らない:
if (value?.isEmpty()) // 駄目. value が null の場合戻り値が null となるので評価値が Boolean? となる
Kotlin は null-safety な言語なので上記のような書き方をすると value
が null
だった場合に NullPointerException
がスローされずに isEmpty()
メソッド呼び出しが行われないという挙動になる。
よって null
の場合を ?:
で付け加えるのは自然に見える。
以下再掲する:
if (value?.isEmpty() == true) // なんとなく null == true がマズそうに見える
Kotlin では全てのローカル変数はオブジェクトの為 null == true
の評価が可能である。
しかし ==
が null
オブジェクトに対する equals()
のシンタックスシュガーに見えるので何となく null
に対するメソッド呼び出しに失敗しそうに見えるのだが、Kotlin 公式に以下のように記載があった:
Structural equality is checked by the
==
operation (and its negated counterpart!=
). By convention, an expression likea == b
is translated toa?.equals(b) ?: (b === null)
つまり Kotlin においては前述の if
文は以下と等価である:
if (value?.isEmpty()?.equals(true) ?: (true === null))
これは比較対象の左辺が null
だった場合左辺と右辺が共に null
の時のみ true
となる、と読み解ける。
つまり Java における「null
は true
とも false
とも一致しないし null == null = true
である」という原則を生かしつつ、常にそのまま第三の値として ==
で比較できるように言語の文法レベルで配慮が効いているということだろう。