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.TRUEBoolean.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 では nulltrue でも false でもない第三の値である。 一方以下のように nullnull の同値比較は true となる事に注意する:

System.out.println(null == null);  // true

Kotlin に話を戻す

話を戻すが Kotlin では以下だとコンパイルが通らない:

if (value?.isEmpty())  // 駄目. value が null の場合戻り値が null となるので評価値が Boolean? となる

Kotlin は null-safety な言語なので上記のような書き方をすると valuenull だった場合に 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 like a == b is translated to a?.equals(b) ?: (b === null)

つまり Kotlin においては前述の if 文は以下と等価である:

if (value?.isEmpty()?.equals(true) ?: (true === null))

これは比較対象の左辺が null だった場合左辺と右辺が共に null の時のみ true となる、と読み解ける。 つまり Java における「nulltrue とも false とも一致しないし null == null = true である」という原則を生かしつつ、常にそのまま第三の値として == で比較できるように言語の文法レベルで配慮が効いているということだろう。