Kotlin で null 可能性を使用する

1. 始める前に

この Codelab では、null 可能性と、null 安全の重要性について学習します。null 可能性は、多くのプログラミング言語で一般的に見られる概念です。これは、値を持たないことが可能であるという、変数の能力のことです。Kotlin では、null 安全を達成するために、null 可能性を意識的に取り扱っています。

前提条件

  • Kotlin プログラミングの基礎知識(変数を含む)、変数からメソッドやプロパティへのアクセスに関する知識、println() 関数と main() 関数の知識
  • if/else 文やブール式などの Kotlin の条件構文に慣れていること

学習内容

  • null とは何か
  • null 値許容型と null 値非許容型の違い
  • null 安全とその重要性、Kotlin がどのように null 安全を実現するか
  • セーフコール演算子(?.)と非 null 値アサーション演算子(!!)を使用して null 許容変数のメソッドとプロパティにアクセスする方法
  • if/else 条件構文を使用して null チェックを行う方法
  • if/else 式を使用して null 値許容変数を null 値非許容型に変換する方法
  • null 値許容変数が null の場合のデフォルト値を、if/else 式またはエルビス演算子(?:)で指定する方法

必要なもの

  • Kotlin プレイグラウンドにアクセスできるウェブブラウザ

2. null 値許容変数を使用する

null とは何か

ユニット 1 では、変数を宣言する場合、すぐに値を代入する必要があることを学習しました。たとえば、favoriteActor 変数を宣言する場合に、すぐに "Sandra Oh" という文字列値を代入します。

val favoriteActor = "Sandra Oh"

「Sandra Oh」という文字列値が代入されている favoriteActor 変数を表す箱

では、お気に入りの俳優がいない場合はどうでしょうか。この変数に "Nobody""None" という値を代入することもできます。しかし、プログラムは favoriteActor 変数に値がないとは解釈せず、"Nobody""None" という値があると解釈するため、良い方法ではありません。Kotlin では、null を使用することで、変数に関連付けられた値がないことを示すことができます。

null 値が代入されている favoriteActor 変数を表す箱

以下のようにして、コードで null を使用しましょう。

  1. Kotlin のプレイグラウンドで、main() 関数の本体の内容を、null に設定された favoriteActor 変数に置き換えます。
fun main() {
    val favoriteActor = null
}
  1. favoriteActor 変数の値を println() 関数で出力するようにしてから、このプログラムを実行します。
fun main() {
    val favoriteActor = null
    println(favoriteActor)
}

出力は、次のコード スニペットのようになります。

null

変数に null を再代入する

以前に、var キーワードで定義された変数には、同じ型の異なる値を再代入できることを学習しました。たとえば、新しい俳優の名前が String 型である限り、ある名前で宣言されている name 変数に別の名前を再代入できます。

var favoriteActor: String = "Sandra Oh"
favoriteActor = "Meryl Streep"

変数を宣言した後で、その変数に null を代入する必要が生じることがあります。たとえば、お気に入りの俳優を宣言した後で、それを一切公開しないことにした場合などです。この場合、favoriteActor 変数への null の代入が使えます。

null 値非許容変数と null 値許容変数について

以下のようにして favoriteActor 変数に null を再代入しましょう。

  1. val キーワードを var キーワードに変更し、favoriteActor 変数が String 型であることを指定して、お気に入りの俳優の名前を代入します。
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor)
}
  1. println() 関数を削除します。
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. favoriteActor 変数に null を再代入するようにしてから、このプログラムを実行します。
fun main() {
    var favoriteActor: String = "Sandra Oh"
    favoriteActor = null
}

次のエラー メッセージが表示されます。

「null を null 値非許容型の String の値にすることはできません」という意味の警告メッセージが表示される。

Kotlin では、null 値許容型と null 値非許容型の区別があります。

  • null 値許容型は、null を保持できる変数です。
  • null 値非許容型は、null を保持できない変数です。

型が null 値許容なのは、それに null を明示的に保持させる場合のみです。エラー メッセージが示すように、String データ型は null 値非許容型であるため、この変数に null を再代入することはできません。

null 値許容型の変数を宣言する方法を示す図。var キーワードで始まり、その後に変数ブロックの名前、セミコロン、変数の型、疑問符、等号、値ブロックと続きます。型ブロックと疑問符で Nullable 型テキストを表しています。型の後に疑問符を付けて null 値許容型であることを表しています。

Kotlin で null 値許容変数を宣言するには、型の末尾に ? 演算子を追加する必要があります。たとえば、String? 型は文字列か null のいずれかを保持できますが、String 型は文字列しか保持できません。null 値許容変数を宣言するには、null 値許容型を明示的に追加する必要があります。null 値許容型でない場合、Kotlin コンパイラは null 値非許容型だと推論します。

  1. favoriteActor 変数の型を String データ型から String? データ型に変更します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    favoriteActor = null
}
  1. null の再代入の前後で favoriteActor 変数を出力するようにしてから、このプログラムを実行します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor)

    favoriteActor = null
    println(favoriteActor)
}

出力は、次のコード スニペットのようになります。

Sandra Oh
null

favoriteActor 変数は、最初は文字列を保持していましたが、その後 null に変わります。

試してみる

null 値許容の String? 型を使用できるようになったので、Int 値で変数を初期化してから、null を再代入しましょう。

null 値許容の Int 値を記述する

  1. main() 関数内のすべてのコードを削除します。
fun main() {

}
  1. null 値許容の Int 型の number 変数を作成し、10 という値を代入します。
fun main() {
    var number: Int? = 10
}
  1. number 変数を出力するようにしてから、このプログラムを実行します。
fun main() {
    var number: Int? = 10
    println(number)
}

出力は想定どおりです。

10
  1. number 変数が null 値許容であることを確かめるために、null を再代入します。
fun main() {
    var number: Int? = 10
    println(number)

    number = null
}
  1. プログラムの最後の行として別の println(number) 文を追加し、実行します。
fun main() {
    var number: Int? = 10
    println(number)

    number = null
    println(number)
}

出力は想定どおりです。

10
null

3. null 値許容変数の扱い方

以前に、「.」演算子を使用して null 値非許容変数のメソッドとプロパティにアクセスする方法を学習しました。このセクションでは、null 値許容変数のメソッドとプロパティにアクセスする方法について説明します。

以下の手順で、null 値非許容の favoriteActor 変数のプロパティにアクセスしましょう。

  1. main() 関数のすべてのコードを削除し、String 型の favoriteActor 変数を宣言して、お気に入りの俳優の名前を代入します。
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. length プロパティを使って favoriteActor 変数値の文字数を出力するようにしてから、このプログラムを実行します。
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor.length)
}

出力は想定どおりです。

9

favoriteActor 変数にはスペースを含めて 9 文字が入っています。文字数はお気に入りの俳優の名前によって異なります。

null 値許容変数のプロパティにアクセスする

favoriteActor 変数を null 値許容にして、お気に入りの俳優がいない場合は、この変数に null を代入できるようにすることを考えます。

以下の手順で、null 値許容の favoriteActor 変数のプロパティにアクセスしましょう。

  • favoriteActor 変数の型を null 値許容型に変更してから、このプログラムを実行します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor.length)
}

次のエラー メッセージが表示されます。

「String 型の null 値許容レシーバーには、セーフコールまたは非 null 値アサーション付きの呼び出しのみが許可されます」という意味のエラー メッセージが表示されます。

このエラーはコンパイル エラーです。前の Codelab で説明したように、コンパイル エラーは、コードの構文エラーが原因で Kotlin がコードをコンパイルできない場合に発生します。

Kotlin では、null 安全null の可能性がある変数での呼び出しが間違って行われないことが保証されているという意味)を達成できるように構文上のルールが意図的に適用されます。これは、変数を null にできないという意味ではありません。変数のメンバーがアクセスされる場合には、その変数を null にできないという意味です。

このことは、アプリの実行中に null である変数のメンバーへのアクセス(null 参照)を行おうとすると、null 変数にはプロパティやメソッドがないためアプリがクラッシュすることから、大変重要です。この種のクラッシュは、コードをコンパイルして実行したあとで発生するエラーであることから、ランタイム エラーと呼ばれています。

Kotlin には null 安全という性質があるため、Kotlin コンパイラが null 値許容型に対する null チェックを強制して、このようなランタイム エラーを防いでいます。Null チェックとは、変数にアクセスして null 値非許容型として扱う前に、その変数が null になる可能性をチェックする処理のことです。null 値許容型を null 値非許容型として使用する場合は、null チェックを明示的に行う必要があります。詳しくは、この Codelab の後半にある if/else 条件構文使用に関するセクションをご覧ください。

この例の場合、favoriteActor 変数の length プロパティを直接参照できないことが原因でコンパイル時エラーとなります。これは、変数が null になる可能性があるためです。

次は、null 値許容型を扱うさまざまな手法と演算子について学習します。

セーフコール演算子?.)を使用する

セーフコール演算子(?.)使用して、null 値許容変数のメソッドやプロパティにアクセスできます。

null 値許容変数のブロックの後に、疑問符、ドット、メソッドまたはプロパティのブロックが続く図。間にスペースはありません。

セーフコール演算子(?.)を使用してメソッドやプロパティにアクセスするには、変数名の後に「?」を追加し、「.」記法でメソッドやプロパティにアクセスします。

セーフコール演算子(?.)を使用すると、Kotlin コンパイラが null 参照に対するメンバー アクセスをすべて阻止して、アクセスされたメンバーの代わりに null を返すため、null 値許容変数に安全にアクセスできます。

以下の手順で、null 値許容の favoriteActor 変数のプロパティに安全にアクセスしましょう。

  1. println() 文で、「.」演算子をセーフコール演算子(?.)に置き換えます。
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor?.length)
}
  1. このプログラムを実行して、出力が予想どおりであることを確認します。
9

文字数はお気に入りの俳優の名前によって異なります。

  1. favoriteActor 変数に null を再代入するようにしてから、このプログラムを実行します。
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor?.length)
}

以下のような出力が表示されます。

null

null 変数の length プロパティにアクセスしようとしましたが、プログラムはクラッシュしていません。セーフコール式は null を返しています。

非 null アサーション演算子(!!)を使用する

null 値許容変数のメソッドやプロパティにアクセスするために、非 null アサーション演算子(!!)を使用することもできます。

null 値許容変数のブロックの後に、感嘆符 2 つ、ドット 1 つ、メソッドまたはプロパティのブロックが続く図。間にスペースはありません。

null 値許容変数の後に、非 null アサーション演算子(!!)を追加し、その後に「.」演算子を追加して、さらにその後にメソッドまたはプロパティを追加する必要があります(スペースは入れません)。

その名前のとおり、非 null アサーション(!!)を使用すると、変数の値がどちらであるかにかかわらず、null でないことをアサートすることになります。

セーフコール演算子(?.)とは異なり、非 null アサーション演算子(!!)を使用すると、null 値許容変数が実際に null の場合に NullPointerException エラーがスローされます。したがって、変数が常に null 値非許容の場合か、適切な例外処理が設定されている場合にのみ行う必要があります。処理しない場合は、例外によりランタイム エラーが発生します。例外処理については、このコースの後半のユニットで説明します。

以下の手順で、非 null アサーション演算子(!!)を使用して favoriteActor 変数のプロパティにアクセスしましょう。

  1. favoriteActor 変数にお気に入りの俳優の名前を再代入し、println() 文のセーフコール演算子(?.)を非 null アサーション演算子(!!)に置き換えます。
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor!!.length)
}
  1. このプログラムを実行して、出力が予想どおりであることを確認します。
9

文字数はお気に入りの俳優の名前によって異なります。

  1. favoriteActor 変数に null を再代入するようにしてから、このプログラムを実行します。
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor!!.length)
}

NullPointerException エラーが発生します。

「スレッド "main" で java.lang.NullPointerException という例外が発生しました」という意味のエラー メッセージ。

この Kotlin エラーは、プログラムが実行中にクラッシュしたことを示しています。このため、変数が null でないことが確実な場合を除き、非 null アサーション演算子(!!)の使用はおすすめしません。

if/else 条件構文を使用する

if/else 条件構文の if 分岐を使って、null チェックを行うことができます。

null 値許容変数のブロックの後に感嘆符、等号、null が続く図。

null チェックは、!= 比較演算子で null 値許容変数が null と等しくないことを確認することで行うことができます。

if/else

次のように if/else 文を null チェックと組み合わせて使用できます。

if キーワードの後に、括弧で囲まれた null チェック ブロック、中括弧のペアで囲まれた本体 1、else キーワード、中括弧のペアで囲まれた本体 2 のブロックと続く、if/else 文を示す図。else 句は赤い点線で囲まれ、省略可能であることが示されています。

null チェックは、if/else 文と組み合わせると便利です。

  • nullableVariable != null という式の null チェックを if の条件として使用します。
  • if 分岐の本体 1 では、変数が null 値非許容の値であると想定されます。したがって、この本体では null 値非許容変数であるかのように変数のメソッドやプロパティに自由にアクセスできます。セーフコール演算子(?.)や非 null アサーション演算子(!!)は不要です。
  • else 分岐の本体 2 では、変数が null であると想定されます。したがって、この本体には、変数が null のときに実行する文を追加できます。else 分岐は省略可能です。null チェックが失敗した場合のデフォルト動作を指定せずに、null チェックを実行する if 条件構文のみを使用できます。

null 値許容変数を使用するコードが複数行ある場合は、if 条件で null チェックを使用するのが便利です。これに対して、null 値許容変数の参照が 1 回の場合は、セーフコール演算子(?.)が便利です。

以下の手順で、favoriteActor 変数の null チェックを含む if/else 文を記述しましょう。

  1. 今回も favoriteActor 変数にお気に入りの俳優の名前を代入し、println() 文を削除します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

}
  1. favoriteActor != null という条件の if 分岐を追加します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {

    }
}
  1. if 分岐の本体に、"The number of characters in your favorite actor's name is ${favoriteActor.length}" 文字列を受け取る println 文を追加してから、このプログラムを実行します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    }
}

出力は想定どおりです。

The number of characters in your favorite actor's name is 9.

文字数はお気に入りの俳優の名前によって異なります。

null チェックの後の if 分岐内で length メソッドにアクセスするため、「.」演算子を使用して名前の length メソッドに直接アクセスできています。このように、Kotlin コンパイラは favoriteActor 変数が null になる可能性がないことを認識しているため、プロパティに直接アクセスすることを許しています。

  1. 省略可: else 分岐を追加して、俳優の名前が null になる状況に対応します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {

    }
}
  1. else 分岐の本体に、"You didn't input a name." 文字列を受け取る println 文を追加します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. favoriteActor 変数に null を代入するようにしてから、このプログラムを実行します。
fun main() {
    var favoriteActor: String? = null

    if(favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}

出力は想定どおりです。

You didn't input a name.

if/else

null チェックと if/else 式を組み合わせて、null 値許容変数を null 値非許容変数に変換することもできます。

val キーワードの後に名前ブロック、コロン、null 値非許可型のブロック、等号、if キーワード、括弧で囲まれた条件、中括弧のペアで囲まれた本体 1、else キーワード、中括弧で囲まれた本体 2 と続く if/else 式を示す図。

次のようにして、null 値非許容型に if/else 式を代入します。

  • nullableVariable != null という null チェックを if 条件として使用します。
  • if 分岐の本体 1 では、変数が null 値非許容の値であると想定されます。したがって、この本体では null 値非許容変数であるかのように変数のメソッドやプロパティにアクセスできます。セーフコール演算子(?.)や非 null アサーション演算子(!!)は不要です。
  • else 分岐の本体 2 では、変数が null であると想定されます。したがって、この本体には、変数が null のときに実行する文を追加できます。
  • 本体 1 と本体 2 の最後の行では、null 値非許容型になる式や値を使用し、それぞれ null チェックが成功したときと失敗したときに、それが null 値非許容変数に代入されるようにする必要があります。

以下の手順で、if/else 式を使用して、println 文を 1 つだけ使用するようにプログラムを書き換えましょう。

  1. お気に入りの俳優の名前を favoriteActor 変数に代入します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. lengthOfName 変数を作成してから、if/else 式を代入します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. if 分岐と else 分岐の両方から println() 文を削除します。
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {

    } else {

    }
}
  1. if 分岐の本体に、favoriteActor.length という式を追加します。
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {

    }
}

favoriteActor 変数の length プロパティには、「.」演算子で直接アクセスします。

  1. else 分岐の本体に、0 の値を追加します。
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }
}

0 の値は、名前が null の場合のデフォルト値として使用されます。

  1. main() 関数の最後に、"The number of characters in your favorite actor's name is $lengthOfName." という文字列を受け取る println 文を追加し、次のプログラムを実行します。
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

出力は想定どおりです。

The number of characters in your favorite actor's name is 9.

文字数は名前によって異なります。

エルビス演算子?:)を使用する

エルビス演算子(?:)は、セーフコール演算子(?.)と合わせて使用できる演算子です。エルビス演算子(?:)を使用することで、セーフコール演算子(?.)が null を返したときのデフォルト値を追加できます。これは if/else 式に似ていますが、より Kotlin らしい書き方です。

変数が null でない場合、エルビス演算子(?:)の前の式が実行されます。変数が null の場合、エルビス演算子(?:)の後の式が実行されます。

val キーワードの後に、名前のブロック、等号、null 値許容変数のブロック、疑問符、ドット、メソッドまたはプロパティのブロック、疑問符、コロン、デフォルト値のブロックと続く図。

以下の手順で、以前のプログラムをエルビス演算子(?:)を使用するように変更しましょう。

  1. if/else 条件構文を削除して、lengthOfName 変数に null 値許容の favoriteActor 変数を設定し、セーフコール演算子(?.)演算子を使用して length プロパティを呼び出します。
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}
  1. length プロパティの後で、エルビス演算子(?:)の後に 0 の値を追加してから、このプログラムを実行します。
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length ?: 0

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

出力は前の出力と同じです。

The number of characters in your favorite actor's name is 9.

4. まとめ

これで、null 可能性と、それをさまざまな演算子で扱う方法の学習が終わりました。

まとめ

  • 変数に null を設定することで、値を保持していないことを示せます。
  • null 値非許容変数には null を代入できません。
  • null 値許容変数には null を代入できます。
  • null 値許容変数のメソッドやプロパティにアクセスするには、セーフコール演算子(?.)か非 null アサーション演算子(!!)を使用する必要があります。
  • null チェックと合わせて if/else 文を使用すると、null 値非許容のコンテキストで null 値許容変数にアクセスできます。
  • if/else 式を使用すると null 値許容変数を null 値非許容型に変換することができます。
  • if/else 式またはエルビス演算子(?:)を使用して、null 値許容変数が null の場合のデフォルト値を指定できます。

さらに詳しく学習するには