191

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

本当に never であることが確実にわかっていると仮定してnull、チェックを必要とせずに値を not として変更または処理するにはどうすればよいですか? たとえば、ここでは、存在することを保証できるマップから値を取得していますが、結果はis notです。しかし、私はエラーがあります:ifnullget()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 許容にしないようにする最善の方法は何ですか?

注: この質問は、作者によって意図的に書かれ、回答されているため ( Self-Answered Questions )、よくある Kotlin トピックに対する慣用的な回答が SO に含まれています。また、現在の Kotlin では正確ではない、Kotlin のアルファ版用に書かれたいくつかの非常に古い回答を明確にするために。

4

4 に答える 4

321

まず、ケースを徹底的にカバーしている Kotlin のNull Safetyについてすべて読む必要があります。

Kotlin では、null 許容値ではないことを確認するnull(条件で null をチェックする) か、確実にsure 演算子nullを使用していないことを主張するか、 Safe Callでアクセスするか、最後におそらくElvis Operatorを使用したデフォルト値。!!?.null?:

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

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、開発者として結果が決して ではないことを確信している場合は、 sure 演算子をアサーションとして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

背景情報:

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

!!Sure 演算子の詳細

!!オペレーターは、値が NPE でないか、NPE をスローしないことをアサートしますnull。これは、開発者が値が決して にならないことを保証している場合に使用する必要がありますnull。assert の後にスマート キャストが続くと考えてください。

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 をチェックする


?.セーフ コール オペレータの詳細

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

続きを読む:エルヴィス オペレーター


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

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 の Promises ライブラリは、promiseの形で同じことを行います。

  • null戻り値の型としてのコレクションの場合、「存在しない」という 3 番目の状態が必要でない限り、常に ではなく空のコレクションを返します。Kotlin には、これらの空の値を作成するためのemptyList()またはなどのヘルパー関数があります。emptySet()

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

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

  • String?.isNullOrEmpty()forやString?.isNullOrBlank()null 許容値を安全に操作し、期待どおりの動作をすることができる関数など、すでに役立つ関数を見てください。実際、独自の拡張機能を追加して、標準ライブラリのギャップを埋めることができます。

  • 標準ライブラリのcheckNotNull()やのようなアサーション関数。requireNotNull()

  • filterNotNull()コレクションから null を削除するヘルパー関数やlistOfNotNull()、可能性のある値からゼロまたは単一の項目リストを返すヘルパー関数null

  • 安全な (null 許容) キャスト演算子もあり、null 非許容型へのキャストが不可能な場合は null を返すことができます。しかし、上記の他の方法で解決されない有効なユースケースはありません。

于 2015-12-28T18:13:06.970 に答える