Skip to content

型チェックとキャスト

Kotlinでは、実行時にオブジェクトの型をチェックする型チェックを実行できます。型キャストを使用すると、オブジェクトを別の型に変換できます。

TIP

ジェネリクスの型チェックとキャスト(例:List<T>Map<K,V>)について具体的に学ぶには、ジェネリクス型チェックとキャストを参照してください。

is および !is 演算子

オブジェクトが指定された型に適合するかどうかを識別する実行時チェックを実行するには、is演算子またはその否定形である!isを使用します。

kotlin
if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // Same as !(obj is String)
    print("Not a String")
} else {
    print(obj.length)
}

スマートキャスト

ほとんどの場合、コンパイラがオブジェクトを自動的にキャストしてくれるため、明示的なキャスト演算子を使用する必要はありません。 これをスマートキャストと呼びます。コンパイラは、不変な値の型チェックと明示的なキャストを追跡し、必要に応じて暗黙的な(安全な)キャストを自動的に挿入します。

kotlin
fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x is automatically cast to String
    }
}

コンパイラは、否定チェックがreturnにつながる場合にキャストが安全であることを認識するほどスマートです。

kotlin
if (x !is String) return

print(x.length) // x is automatically cast to String

制御フロー

スマートキャストは、if条件式だけでなく、whenwhileループでも機能します。

kotlin
when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

Boolean型の変数をifwhen、またはwhile条件で使用する前に宣言すると、コンパイラがその変数について収集した情報が、スマートキャストのために対応するブロックでアクセス可能になります。

これは、論理条件を変数に抽出したい場合などに役立ちます。そうすることで、変数に意味のある名前を付けられ、コードの可読性が向上し、後でコード内で変数を再利用できるようになります。例:

kotlin
class Cat {
    fun purr() {
        println("Purr purr")
    }
}

fun petAnimal(animal: Any) {
    val isCat = animal is Cat
    if (isCat) {
        // コンパイラはisCatに関する情報にアクセスできるため、
        // animalがCat型にスマートキャストされたことを認識します。
        // したがって、purr()関数を呼び出すことができます。
        animal.purr()
    }
}

fun main(){
    val kitty = Cat()
    petAnimal(kitty)
    // Purr purr
}

論理演算子

コンパイラは、&&または||演算子の左辺に型チェック(通常または否定)がある場合、右辺でスマートキャストを実行できます。

kotlin
// `||`の右辺でxは自動的にStringにキャストされます
if (x !is String || x.length == 0) return

// `&&`の右辺でxは自動的にStringにキャストされます
if (x is String && x.length > 0) {
    print(x.length) // xは自動的にStringにキャストされます
}

オブジェクトの型チェックをor演算子(||)と組み合わせると、最も近い共通のスーパータイプにスマートキャストされます。

kotlin
interface Status {
    fun signal() {}
}

interface Ok : Status
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {
    if (signalStatus is Postponed || signalStatus is Declined) {
        // signalStatusは共通のスーパータイプStatusにスマートキャストされます
        signalStatus.signal()
    }
}

NOTE

共通のスーパータイプは、ユニオン型近似です。ユニオン型は現在Kotlinではサポートされていません

インライン関数

コンパイラは、インライン関数に渡されるラムダ関数内でキャプチャされた変数をスマートキャストできます。

インライン関数は、暗黙的なcallsInPlaceコントラクトを持つものとして扱われます。これは、インライン関数に渡されるラムダ関数がインプレースで呼び出されることを意味します。ラムダ関数はインプレースで呼び出されるため、コンパイラはラムダ関数がその関数本体に含まれる変数の参照を漏洩できないことを認識しています。

コンパイラは、この知識と他の分析を組み合わせて、キャプチャされた変数をスマートキャストしても安全かどうかを判断します。例:

kotlin
interface Processor {
    fun process()
}

inline fun inlineAction(f: () -> Unit) = f()

fun nextProcessor(): Processor? = null

fun runProcessor(): Processor? {
    var processor: Processor? = null
    inlineAction {
        // コンパイラはprocessorがローカル変数であり、inlineAction()がインライン関数であることを認識しているため、
        // processorへの参照が漏洩することはありません。
        // したがって、processorをスマートキャストしても安全です。
      
        // processorがnullでない場合、processorはスマートキャストされます
        if (processor != null) {
            // コンパイラはprocessorがnullでないことを認識しているため、
            // セーフコールは不要です
            processor.process()
        }

        processor = nextProcessor()
    }

    return processor
}

例外処理

スマートキャスト情報はcatchブロックとfinallyブロックに渡されます。これにより、コンパイラがオブジェクトがNull許容型であるかどうかを追跡するため、コードの安全性が向上します。例:

kotlin
fun testString() {
    var stringInput: String? = null
    // stringInputはString型にスマートキャストされます
    stringInput = ""
    try {
        // コンパイラはstringInputがnullでないことを認識しています
        println(stringInput.length)
        // 0

        // コンパイラはstringInputの以前のスマートキャスト情報を破棄します。
        // これでstringInputはString?型になります。
        stringInput = null

        // 例外をトリガー
        if (2 > 1) throw Exception()
        stringInput = ""
    } catch (exception: Exception) {
        // コンパイラはstringInputがnullになり得ることを認識しているため、
        // stringInputはNull許容のままです。
        println(stringInput?.length)
        // null
    }
}
fun main() {
    testString()
}

スマートキャストの前提条件

DANGER

スマートキャストは、コンパイラが、変数がチェックと使用の間に変更されないことを保証できる場合にのみ機能することに注意してください。

スマートキャストは以下の条件下で使用できます。

val ローカル変数 常に、ローカルのデリゲートプロパティを除く。
val プロパティ プロパティがprivateinternalである場合、またはプロパティが宣言されている同じモジュールでチェックが実行される場合。スマートキャストはopenプロパティやカスタムゲッターを持つプロパティには使用できません。
var ローカル変数 変数がチェックと使用の間で変更されない場合、それを変更するラムダでキャプチャされない場合、およびローカルのデリゲートプロパティではない場合。
var プロパティ 変数がいつでも他のコードによって変更される可能性があるため、使用できません。

「安全でない (unsafe)」キャスト演算子

オブジェクトをNull非許容型に明示的にキャストするには、安全でないキャスト演算子asを使用します。

kotlin
val x: String = y as String

キャストが不可能な場合、コンパイラは例外をスローします。これが安全でないと呼ばれる理由です。

前の例でynullの場合、上記のコードも例外をスローします。これは、StringNull許容ではないため、nullStringにキャストできないためです。Null値の可能性がある例を機能させるには、キャストの右辺でNull許容型を使用します。

kotlin
val x: String? = y as String?

「安全な (safe)」(Null許容)キャスト演算子

例外を避けるには、失敗時にnullを返す安全なキャスト演算子as?を使用します。

kotlin
val x: String? = y as? String

as?の右辺がNull非許容型Stringであるにもかかわらず、キャストの結果はNull許容になることに注意してください。