web-dev-qa-db-ja.com

Kotlinでは、null値を参照、変換する慣用的な方法は何ですか

ヌル可能タイプXyz?がある場合、それを参照するか、ヌル不可タイプ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?を返します。したがって、値の型を強制的にnullにできないようにするための最良の方法は何ですか?

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

149
Jayson Minard

最初に、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の質問で、開発者として結果が決してnullにならないことを確信している場合、!! sure演算子をアサーション:

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

または、マップCOULDが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演算子の詳細

!!演算子は、値が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
}

または、null不可タイプのisチェックを行う場合:

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ではなく空でない場合にのみ、もう1つの例は、安全な呼び出し演算子が役立ちます:

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チェックを行ったが、別のスレッドが値を変更する可能性があるため、 smart cast がなかった場合がありました。この問題を解決するには、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
    }
}

続きを読む: Safe Calls


?: 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
}

続きを読む: エルビス演算子


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

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の形式で同じことを行います。

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

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

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

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

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

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

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

257
Jayson Minard

前の答えは従うのが難しい行為ですが、ここに1つの迅速で簡単な方法があります。

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

本当にnullにならない場合、例外は発生しませんが、実際にnullである場合は、何が間違っているのかがわかります。

2
John Farrell