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にはないなんて、なんか不便じゃないですか?
なので今回は、三項演算子っぽいものを無理矢理作ってみたいと思います
三項演算子を定義する
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
なんかそれっぽい感じになりました
Javaの三項演算子との違い
演算子の優先度
Kotlinのinfix関数は、比較演算子よりも優先度が高いです。
https://kotlinlang.org/docs/reference/grammar.html#precedence
一方で、Javaの三項演算子は、比較演算子よりも優先度が低いです。
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html
そのため、今回作った三項演算子もどきで比較演算子などを使う場合は、条件式をカッコでくくる必要があります。
int max = a > b ? a : b; // OK
val max = a > b `?` a `:` b // NG
val max = (a > b) `?` a `:` b // OK
val max = (a > b) `?` a + 3 `:` b - 5 // OK
式の評価
saka1029さんのコメントで気が付きました。ありがとうございます
今回作った三項演算子もどきは、値を渡しているため、条件の真偽にかかわらず両方の値が評価されてしまいます。
int value = array.length > 0 ? array[0] : -1; // OK
val value = array.isNotEmpty() `?` array[0] `:` -1 // arrayが空のときArrayIndexOutOfBoundsException
これを回避するとなると、T
型ではなく() -> T
型にしないといけないですが、もはやif式との違いがなくなってしまいますね
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
の時の値になってしまいます
val body = response.isError() then null ?: response.body // always response.body
また、先ほどと同じように、優先度の問題と、式の評価の問題があります。
まとめ
ネタで作ってみましたが、やっぱり不便ですね
自分はPythonもよく使うので、Kotlinの三項演算子の書き方はまだ慣れないです
何か感想や意見がありましたらコメントお願いします