1. ホーム
  2. nullable

[解決済み] Kotlinで、nullableな値を扱う慣用的な方法、参照または変換する方法は何ですか?

2022-04-13 09:21:42

質問

null可能な型がある場合 Xyz? それを参照するか、NULLでない型に変換したい。 Xyz . Kotlinでそのようなことをする慣用的な方法は何ですか?

例えば、このようなコードはエラーになります。

val something: Xyz? = createPossiblyNullXyz()
something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?"

しかし、最初にnullをチェックすると許可されるのですが、なぜですか?

val something: Xyz? = createPossiblyNullXyz()
if (something != null) {
    something.foo() 
}

値を変更したり、そうでないものとして扱うにはどうしたらよいでしょうか。 null を必要とせず if をチェックし、それが本当に決してそうでないことを確実に知っていると仮定します。 null ? 例えば、ここでは存在することが保証されているマップから値を取得し、その結果を get()null . しかし、私はエラーが発生しました。

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")
something.toLong() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?"

メソッド get() は、その項目が欠落している可能性があると考え、タイプ Int? . したがって、値の型を強制的にnullableでないものにする最善の方法は何でしょうか?

注意してください。 この質問は、意図的に筆者が書き、回答しています( 自問自答 ) のように、よく聞かれるKotlinのトピックに対するイディオム的な回答がSOに存在するようにします。 また、Kotlinのアルファ版として書かれた、現在のKotlinでは正確ではない本当に古い答えを明らかにするためでもあります。

解決方法は?

まずは Nullの安全性 のKotlinでは、このケースを徹底的にカバーしています。

Kotlinでは、NULL可能な値に対して、その値が null ( 条件付きでNULLをチェックする )、あるいは「きっと違う」と断言する。 null を使って !! 確かな演算子 でアクセスすると ?. 安全な呼び出し あるいは最後に、もしかしたらと思うようなものを与える null を使用してデフォルト値を設定します。 ?: エルビス演算子 .

ご質問の1つ目のケースについて コードの意図に応じて、これらのいずれかを使用するオプションがあります。

val something: Xyz? = createPossiblyNullXyz()

// access it as non-null asserting that with a sure call
val result1 = something!!.foo()

// access it only if it is not null using safe operator, 
// returning null otherwise
val result2 = something?.foo()

// access it only if it is not null using safe operator, 
// otherwise a default value using the elvis operator
val result3 = something?.foo() ?: differentValue

// null check it with `if` expression and then use the value, 
// similar to result3 but for more complex cases harder to do in one expression
val result4 = if (something != null) {
                   something.foo() 
              } else { 
                   ...
                   differentValue 
              }

// null check it with `if` statement doing a different action
if (something != null) { 
    something.foo() 
} else { 
    someOtherAction() 
}

nullをチェックしても動作する理由"については、以下の背景情報をお読みください。 スマートキャスト .

ご質問の2番目のケースについて の質問で Map もし、開発者として、その結果が決して null を使用します。 !! の演算子をアサーションとして使用します。

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid

また、別のケースとして、マップが null を返す可能性があるが、デフォルト値を提供できる場合、次のようになります。 Map は、それ自体が getOrElse メソッド :

val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.getOrElse("z") { 0 } // provide default value in lambda
something.toLong() // now valid


背景情報です。

注意事項 以下の例では、動作を明確にするために明示的な型を使っています。 型推論では、通常、ローカル変数やプライベートメンバーについては型を省略することができます。

の詳細はこちら !! 確かな演算子

!! 演算子は、その値が null を投げるか、NPEを投げる。 これは、開発者がその値が決して null . これは、アサートの後に スマートキャスト .

val possibleXyz: Xyz? = ...
// assert it is not null, but if it is throw an exception:
val surelyXyz: Xyz = possibleXyz!! 
// same thing but access members after the assertion is made:
possibleXyz!!.foo()

続きを読む !! シュアオペレーター


詳細はこちら null チェックとスマートキャスト

null可能な型へのアクセスを null をチェックすると、コンパイラは スマートキャスト を使用すると、ステートメント本体内の値がNULLでなくなります。 複雑なフローではこのようなことは起こりえませんが、一般的なケースでは問題なく動作します。

val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
   // allowed to reference members:
   possiblyXyz.foo()
   // or also assign as non-nullable type:
   val surelyXyz: Xyz = possibleXyz
}

または is は、NULLでない型をチェックします。

if (possibleXyz is Xyz) {
   // allowed to reference members:
   possiblyXyz.foo()
}

また、セーフキャストを行う 'when' 式についても同様です。

when (possibleXyz) {
    null -> doSomething()
    else -> possibleXyz.foo()
}

// or

when (possibleXyz) {
    is Xyz -> possibleXyz.foo()
    is Alpha -> possibleXyz.dominate()
    is Fish -> possibleXyz.swim() 
}

を許可しないものもあります。 null にチェックします。 スマートキャスト を使用することで、後でその変数を使用することができます。 上の例では、アプリケーションの流れの中で変化する可能性のないローカル変数を使用しています。 val または var に変異する機会はありませんでした。 null . しかし、コンパイラがフロー解析を保証できない他のケースでは、これはエラーになるでしょう。

var nullableInt: Int? = ...

public fun foo() {
    if (nullableInt != null) {
        // Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
        val nonNullableInt: Int = nullableInt
    }
}

変数のライフサイクル nullableInt は完全に見えないので、他のスレッドから代入される可能性があります。 null をチェックすることはできません。 スマートキャスト を NULL 値でない値に変換します。回避策については、以下の「安全な呼び出し」トピックを参照してください。

を信頼できないもう一つのケースは スマートキャスト を変異させないようにするのは val プロパティがあり、そのプロパティにはカスタムゲッターがあります。 この場合、コンパイラは何がその値を変異させたかを知ることができないので、エラーメッセージが表示されます。

class MyThing {
    val possibleXyz: Xyz? 
        get() { ... }
}

// now when referencing this class...

val thing = MyThing()
if (thing.possibleXyz != null) {
   // error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
   thing.possiblyXyz.foo()
}

続きを読む 条件におけるnullのチェック


の詳細については、こちらをご覧ください。 ?. セーフコール・オペレーター

安全な呼び出し演算子は、左側の値が NULL の場合は NULL を返し、そうでない場合は右側の式の評価を続けます。

val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()

もう一つの例として、リストを反復処理したいが、もし null と空でない場合、ここでも安全な呼び出し演算子が役に立ちます。

val things: List? = makeMeAListOrDont()
things?.forEach {
    // this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}

上記の例の一つで、私たちは if をチェックしたが、他のスレッドで値が変異してしまい、そのため スマートキャスト . このサンプルを変更して、セーフコール演算子と let 関数で解決できます。

var possibleXyz: Xyz? = 1

public fun foo() {
    possibleXyz?.let { value ->
        // only called if not null, and the value is captured by the lambda
        val surelyXyz: Xyz = value
    }
}

続きを読む 安全な通話


の詳細については、こちらをご覧ください。 ?: エルビス オペレーター

エルビス演算子では、演算子の左側の式が、以下のような場合に代替値を与えることができます。 null :

val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()

また、いくつかのクリエイティブな使い方もあります。 null :

val currentUser = session.user ?: throw Http401Error("Unauthorized")

または関数から早く戻ることです。

fun foo(key: String): Int {
   val startingCode: String = codes.findKey(key) ?: return 0
   // ...
   return endingValue
}

続きを読む エルビス オペレーター


Null 演算子と関連する関数

Kotlin stdlibには、上記の演算子とうまく連携する関数がいくつも用意されています。 たとえば、以下のようなものです。

// use ?.let() to change a not null value, and ?: to provide a default
val something = possibleNull?.let { it.transform() } ?: defaultSomething

// use ?.apply() to operate further on a value that is not null
possibleNull?.apply {
    func1()
    func2()
}

// use .takeIf or .takeUnless to turn a value null if it meets a predicate
val something = name.takeIf { it.isNotBlank() } ?: defaultName

val something = name.takeUnless { it.isBlank() } ?: defaultName


関連トピック

Kotlin では、ほとんどのアプリケーションで null の値もありますが、常に可能とは限りません。 また、時には null は完全に意味があります。 考えるべきガイドラインをいくつか紹介します。

  • 場合によっては、メソッド呼び出しのステータスと成功した場合の結果を含む、異なる戻り値のタイプを保証します。 以下のようなライブラリは 結果 は、成功または失敗の結果タイプを与え、コードを分岐させることもできます。 また、Kotlin用のPromisesライブラリである コベナント は同じことを約束という形で行っています。

  • の代わりに、常に空のコレクションを返します。 null 第三の状態である "not present" が必要な場合を除き、quot;not present"を指定します。 Kotlinには以下のようなヘルパー関数があります。 emptyList() または emptySet() を使用して、これらの空の値を作成します。

  • NULL 値を返すメソッドで、デフォルト値や代替値がある場合、Elvis 演算子を使用してデフォルト値を指定します。 の場合は Map を使用します。 getOrElse() の代わりにデフォルト値を生成することができます。 Map メソッド get() で、これは null 可能な値を返します。 同じ getOrPut()

  • Java からメソッドをオーバーライドする際、Kotlin が Java コードの null 可能性について確信が持てない場合は、常に ? シグネチャと機能が明確な場合は、オーバーライドから無効化することができます。 したがって、オーバーライドされたメソッドは、より null 安全です。 Java のインターフェースを Kotlin で実装する場合も同じで、有効だとわかっている nullability を変更する。

  • のように、すでに役立つ関数に目を向けてください。 String?.isNullOrEmpty() そして String?.isNullOrBlank() は、NULL 値を安全に操作し、期待通りの結果を得ることができます。 実際、標準ライブラリの隙間を埋めるために、独自の拡張を追加することができます。

  • のようなアサーション関数があります。 checkNotNull() requireNotNull() を標準ライブラリに追加しました。

  • のようなヘルパー関数があります。 filterNotNull() コレクションからヌルを削除する listOfNotNull() からゼロまたは単一のアイテムリストを返すためのものです。 null の値を指定します。

  • があります。 安全な(ヌル可能な)キャスト演算子 また、NULLでない型へのキャストが不可能な場合、NULLを返すようにすることもできます。しかし、私は、上記の他の方法で解決できない有効なユースケースを持っていないのです。