Edited at

Kotlinには三項演算子(条件演算子)がない

More than 1 year has passed since last update.


Kotlinと三項演算子(条件演算子)

GoogleがAndroidの開発言語としてKotlinを公式にサポートしたことで、最近何かと話題のKotlinですが、Kotlinには三項演算子がありません。

val max = a > b ? a : b // NG

そのかわり、Kotlinではifが式として扱われるので、以下のように書くことができます。

val max = if (a > b) a else b

だけど、Javaには三項演算子があるのに、Kotlinにはないなんて、なんか不便じゃないですか? :thinking:

なので今回は、三項演算子っぽいものを無理矢理作ってみたいと思います :muscle:


三項演算子を定義する

Kotlinには?:という演算子はないので、Operator overloadingは使えません。

しかし、infixを使うと、独自の演算子(のように見えるもの)が定義できます。

https://kotlinlang.org/docs/reference/functions.html#infix-notation

今回は、以下のようにクラスとinfix関数を定義します。

data class TernaryOperation<out T>(val condition: Boolean, val value: T)

infix fun <T> Boolean.`?`(other: T) = TernaryOperation<T>(this, other)
infix fun <T> TernaryOperation<T>.`:`(other: T) = if (this.condition) this.value else other

?:は関数名として使えないため、バッククォートで囲っています。

ただ、`?`は使えても、`:`はなぜか使えなかったので、`:`(全角)にしました。

また、KotlinでもJavaと同様に、関数名に日本語を使うことができますが、は使えません。

上記で定義した関数を使うときはこんな感じです。

val max = (a > b) `?` a `:` b

なんかそれっぽい感じになりました :sweat_smile:


Javaの三項演算子との違い


演算子の優先度

Kotlinのinfix関数は、比較演算子よりも優先度が高いです。

https://kotlinlang.org/docs/reference/grammar.html#precedence

一方で、Javaの三項演算子は、比較演算子よりも優先度が低いです。

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html

そのため、今回作った三項演算子もどきで比較演算子などを使う場合は、条件式をカッコでくくる必要があります。


Java

int max = a > b ? a : b; // OK



Kotlin

val max = a > b `?` a `:` b // NG

val max = (a > b) `?` a `:` b // OK
val max = (a > b) `?` a + 3 `:` b - 5 // OK



式の評価

saka1029さんのコメントで気が付きました。ありがとうございます :bow:

今回作った三項演算子もどきは、値を渡しているため、条件の真偽にかかわらず両方の値が評価されてしまいます。


Java

int value = array.length > 0 ? array[0] : -1; // OK



Kotlin

val value = array.isNotEmpty() `?` array[0] `:` -1 // arrayが空のときArrayIndexOutOfBoundsException


これを回避するとなると、T型ではなく() -> T型にしないといけないですが、もはやif式との違いがなくなってしまいますね :sweat_smile:

data class TernaryOperation<out T>(val condition: Boolean, val value: () -> T)

infix fun <T> Boolean.`?`(other: () -> T) = TernaryOperation<T>(this, other)
infix fun <T> TernaryOperation<T>.`:`(other: () -> T) = if (this.condition) this.value() else other()

val value = array.isNotEmpty() `?` { array[0] } `:` { -1 } // OK


エルビス演算子を使うパターン

以下のようなinfix関数を定義します。

infix fun <T> Boolean.then(other: T) = if (this) other else null

エルビス演算子を使うと、以下のように三項演算子っぽく書けます。

val max = (a > b) then a ?: b

ただし、trueの時の値にnullを使うと、(当然ですが)常にfalseの時の値になってしまいます :cry:

val body = response.isError() then null ?: response.body // always response.body

また、先ほどと同じように、優先度の問題と、式の評価の問題があります。


まとめ

ネタで作ってみましたが、やっぱり不便ですね :sweat:

自分はPythonもよく使うので、Kotlinの三項演算子の書き方はまだ慣れないです :sob:

何か感想や意見がありましたらコメントお願いします :bow: