Kotlinでは、null許容値を処理し、それらを参照または変換する慣用的な方法は何ですか


177

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() 
}

値が本当に絶対にないことを確信していると仮定してnullifチェックを必要とせずに値を変更または処理するにはどうすればよいnullですか?たとえば、ここではマップから値を取得していますが、存在が保証され、結果get()はnot 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()は、アイテムが欠落している可能性があると考え、typeを返しますInt?。したがって、値の型をnullにできないようにするための最良の方法は何ですか?

注: この質問は、作者が意図的に作成して回答したものであり(自己回答式の質問)、一般的に尋ねられるKotlinトピックに対する慣用的な回答がSOにあります。また、現在のKotlinには正確ではない、Kotlinのアルファ用に書かれたいくつかの本当に古い回答を明確にするためにも。

回答:


296

最初に、ケースを完全にカバーするKotlinのNull Safetyに関するすべてを読む必要があります。

Kotlinでは、あなたはそれがされていないことを確認することなく、NULL可能値にアクセスすることはできませんnull条件におけるヌルの確認)、またはそれが確実にされていないことを主張するnull使用して!!確認作業を、とそれをアクセスする?.安全なコールの最後に、おそらく何かを与える、またはElvis Operatornullを使用したデフォルト値。?:

質問の最初のケースでは、これらのいずれかを使用するコードの目的に応じてオプションがあり、すべて慣用的ですが結果は異なります。

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チェック時になぜ機能するのか」については、スマートキャストに関する以下の背景情報をお読みください。

あなたの質問であなたの第二の場合のためにとの質問では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
}

または、isnull可能でない型のチェックを行う場合:

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チェックを許可しないものもあります。用途上記の例ない方法でアプリケーションの流れに変異していることができることをローカル変数かどうか、またはこの変数はに変異する機会がなかったです。しかし、コンパイラがフロー分析を保証できない他の場合、これはエラーになります。valvarnull

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のチェック


?.Safe Callオペレーターの詳細

安全な呼び出し演算子は、左側の値が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):
}

上記の例の1つでは、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
    }
}

続きを読む:安全な通話


?:Elvisオペレーターの詳細

Elvis演算子を使用すると、演算子の左側の式が次の場合に代替値を指定できます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
}

続きを読む:Elvisオペレーター


関連する関数を持つヌル演算子

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完全に理にかなっています。考慮すべきいくつかのガイドライン:

  • 場合によっては、メソッド呼び出しのステータスや成功した場合の結果など、さまざまな戻り値の型が保証されます。Resultなどのライブラリーは、コードを分岐できる成功または失敗の結果タイプを提供します。また、Kovenantと呼ばれるKotlinのPromiseライブラリは、約束の形で同じことを行います。

  • 戻り値の型としてのコレクションではnull、「存在しない」という3番目の状態が必要でない限り、の代わりに常に空のコレクションを返します。Kotlinは、次のようなヘルパー機能を持っているemptyList()か、emptySet()これらの空の値を作成します。

  • デフォルトまたは代替のnull許容値を返すメソッドを使用する場合は、Elvis演算子を使用してデフォルト値を指定します。null値を返すメソッドの代わりにデフォルト値を生成できるようにするMap使用の場合。同じgetOrElse()Mapget()getOrPut()

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

  • などのために、すでに助けることができる機能、見String?.isNullOrEmpty()String?.isNullOrBlank()その安全NULL可能値で動作し、あなたが期待するものを行うことができます。実際、独自の拡張機能を追加して、標準ライブラリのギャップを埋めることができます。

  • アサーションは、標準ライブラリcheckNotNull()と同様に機能しrequireNotNull()ます。

  • filterNotNull()コレクションからnullを削除したりlistOfNotNull()、ゼロまたは単一のアイテムリストをnull値から返すヘルパー関数。

  • そこにある金庫(NULL可能)キャスト演算子ができない場合は非NULL可能タイプのリターンnullにキャストを可能にするだけでなく、は。しかし、上記の他の方法で解決されない有効なユースケースはありません。


1

前の答えは従うのが難しい行為ですが、ここにすばやく簡単な方法があります:

val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null")
something.foo() 

それが本当にnullにならない場合、例外は発生しませんが、それがnullである場合、何が問題であったかがわかります。


12
val何か:Xyz = createPossiblyNullXyz()!! createPossiblyNullXyz()がnullを返すと、NPEがスローされます。よりシンプルで、nullでないことがわかっている値を処理するための規則に従います
Steven Waterman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.