Kotlin 2.0のSmart cast改善

Kenji Abe
6 min readApr 14, 2024
Photo by lan deng on Unsplash

Kotlin 2.0ではSmart castが賢くなっているので、簡単に紹介します。2.0未満ではSmart castが効いてない部分も2.0からは効くようになってたりしています。

注意: 2.0.0-RC1 で検証しています。

ローカル変数の条件

以下のようなパターンで改善されています。

fun sample1(obj: Any) {
val isString = obj is String
if (isString) {
// StringにCastされる
println(obj.length)
}
}

これまで、if (obj is String) {} のような if 文の中にある条件式ではSmart castが効いてましたが、外にある条件では効いてませんでした。

これが2.0からはSmart castが可能になっています。 if だけではなく whenwhile でも可能になっています。

ORによる型チェック

次は || を使ったときの型チェックによるSmart castが可能になっています。

interface Animal {
fun bark()
}

class Dog : Animal {
override fun bark() { println("bow wow") }
}

class Cat : Animal {
override fun bark() { println("meow") }
}

fun sample2(obj : Any) {
if (obj is Dog || obj is Cat) {
// 2.0ではAnimalにCastされる。
// 2.0未満ではAnyになるのでコンパイルエラー
obj.bark()
}
}

|| を使っていない if (obj is Dog) {} だけでだとSmart castが可能でしたが、 || を使っている場合は出来ませんでした。これが2.0からは可能になります。

inline関数

inline 関数の扱いについても改善されています。

inline fun inlineFunction(block: () -> Unit) = block()

fun sample3() {
var num: Int? = null
inlineFunction {
if (num != null) {
// 2.0未満はコンパイルエラー、2.0ではOK
num ++
} else {
num = 0
}
}
}

inline 関数はその場で呼び出されるため、それを2.0から認識できるようになったようです。

関数のプロパティ

クラスが保持しているプロパティについての扱いです。

class Sample(val callback: (() -> Unit)?) {
fun run() {
if (callback != null) {
// 2.0未満では、invokeをつける必要がある
                        // 2.0ではそのまま呼び出せる
callback()
}
}
}

2.0未満では invoke を使って呼び出せば問題なく使用できましたが、2.0からは invoke がなくてもそのまま呼び出すことが可能になっています。

catch文

try catchにおけるSmart castの改善です。

fun sample5() {
var string: String? = null
string = ""
try {
println(string.length)

string = null // nullに戻す

error("Sample") // エラーを起こす

} catch (e: Exception) {
// 2.0未満ではビルドが通り、クラッシュしてしまう
                // 2.0ではnullの可能性がわかってるので、ビルドが通らない
                // println(string.length)

// 2.0ではsafe callの必要がある
println(string?.length)
}
}

catchfinally にSmart castの情報を渡せるようになったとこので、より安全なコードを書けるようになっています。

インクリメントとデクリメント

インクリメントとデクリメントしたときのSmart castが可能になっています。

interface Hoge {
operator fun inc(): Foo = Foo()
}

class Foo : Hoge {
fun run() = Unit
}

interface Piyo {
fun exec() = Unit
}

fun sample6(obj: Hoge) {
var unknownObj = obj
if (obj is Piyo) {
// 元の型がHogeなので、incが呼べる
unknownObj++

// incが呼べるということは、実態がFooであることが確定するのでメソッドが呼べる
// 2.0未満ではここでコンパイルエラーになる
unknownObj.run()

// 上記の流れからPiyoではないことは明らかなの、Piyoのメソッドは呼べない
// 2.0未満では、ここではコンパイルエラーにならない
// unknownObj.exec()
}
}

正直ややこしすぎてよくわからんですが…
インクリメントやデクリメントしたときに型が判明したときにSmart castされる感じです。

参考

(Kotlinの whatsnew-eap.html のページは将来的に変わっちゃうので、GitHubのハッシュを指定しています。)

--

--

Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO