Innanzitutto, dovresti leggere tutto sulla sicurezza nulla in Kotlin che copre a fondo i casi.
In Kotlin, non puoi accedere a un valore nullable senza essere sicuro che non lo sia null
( Verifica di null in condizioni ), o affermando che sicuramente non sta null
usando l' !!
operatore sicuro , accedendo con una ?.
chiamata sicura o dando infine qualcosa che è probabilmente null
un valore predefinito utilizzando ?:
Elvis Operator .
Per il tuo primo caso nella tua domanda hai opzioni che dipendono dall'intento del codice che useresti uno di questi, e tutti sono idiomatici ma hanno risultati diversi:
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()
}
Per "Perché funziona quando è selezionato null" leggi le informazioni di base qui sotto su smart cast .
Per il tuo secondo caso nella tua domanda nella domanda con Map
, se come sviluppatore sei sicuro che il risultato non sia mai null
, usa l' !!
operatore sicuro come un'asserzione:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
o in un altro caso, quando la mappa POTREBBE restituire un valore nullo ma è possibile fornire un valore predefinito, allora Map
stesso ha un getOrElse
metodo :
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
Informazioni di base:
Nota: negli esempi seguenti sto usando tipi espliciti per chiarire il comportamento. Con l'inferenza di tipo, normalmente i tipi possono essere omessi per variabili locali e membri privati.
Maggiori informazioni !!
sull'operatore sicuro
L' !!
operatore afferma che il valore non è null
o genera un NPE. Questo dovrebbe essere usato nei casi in cui lo sviluppatore garantisce che il valore non sarà mai null
. Pensalo come un'affermazione seguita da un cast intelligente .
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()
leggi di più: !! Sicuro operatore
Ulteriori informazioni su null
Verifica e Smart Cast
Se si protegge l'accesso a un tipo nullable con un null
segno di spunta, il compilatore eseguirà il cast intelligente del valore all'interno del corpo dell'istruzione come non annullabile. Ci sono alcuni flussi complicati in cui ciò non può accadere, ma per casi comuni funziona bene.
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
O se si esegue un is
controllo per un tipo non annullabile:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
E lo stesso per le espressioni "quando" che trasmettono anche in sicurezza:
when (possibleXyz) {
null -> doSomething()
else -> possibleXyz.foo()
}
// or
when (possibleXyz) {
is Xyz -> possibleXyz.foo()
is Alpha -> possibleXyz.dominate()
is Fish -> possibleXyz.swim()
}
Alcune cose non consentono al null
controllo di eseguire il cast intelligente per l'uso successivo della variabile. L'esempio precedente utilizza una variabile locale che non avrebbe potuto mutare in alcun modo nel flusso dell'applicazione, indipendentemente dal fatto val
che var
questa variabile non avesse l'opportunità di mutare in a null
. Ma, in altri casi in cui il compilatore non può garantire l'analisi del flusso, questo sarebbe un errore:
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
}
}
Il ciclo di vita della variabile nullableInt
non è completamente visibile e può essere assegnato da altri thread, il null
controllo non può essere trasmesso in modo intelligente in un valore non annullabile. Vedere l'argomento "Chiamate sicure" di seguito per una soluzione alternativa.
Un altro caso che non può essere considerato attendibile da un cast intelligente per non mutare è una val
proprietà su un oggetto che ha un getter personalizzato. In questo caso, il compilatore non ha visibilità su ciò che muta il valore e quindi riceverai un messaggio di errore:
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()
}
leggi di più: Verifica di null in condizioni
Ulteriori informazioni ?.
sull'operatore Safe Call
L'operatore di chiamata sicura restituisce null se il valore a sinistra è null, altrimenti continua a valutare l'espressione a destra.
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
Un altro esempio in cui si desidera iterare un elenco, ma solo in caso contrario null
e non vuoto, l'operatore di chiamata sicura può tornare utile:
val things: List? = makeMeAListOrDont()
things?.forEach {
// this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}
In uno degli esempi sopra abbiamo avuto un caso in cui abbiamo fatto un if
controllo ma abbiamo la possibilità che un altro thread abbia mutato il valore e quindi nessun cast intelligente . Possiamo modificare questo esempio per utilizzare l'operatore di chiamata sicura insieme alla let
funzione per risolvere questo:
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
}
}
leggi di più: Chiamate sicure
Ulteriori informazioni su ?:
Elvis Operator
L'operatore Elvis consente di fornire un valore alternativo quando un'espressione a sinistra dell'operatore è null
:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
Ha anche alcuni usi creativi, ad esempio generare un'eccezione quando qualcosa è null
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
o per tornare presto da una funzione:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
// ...
return endingValue
}
leggi di più: Elvis Operator
Operatori null con funzioni correlate
Kotlin stdlib ha una serie di funzioni che funzionano davvero bene con gli operatori sopra menzionati. Per esempio:
// 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
Argomenti correlati
In Kotlin, la maggior parte delle applicazioni cerca di evitare i null
valori, ma non è sempre possibile. E a volte null
ha perfettamente senso. Alcune linee guida a cui pensare:
in alcuni casi, garantisce diversi tipi di restituzione che includono lo stato della chiamata del metodo e il risultato in caso di successo. Le librerie come Risultato forniscono un tipo di risultato di esito positivo o negativo che può anche ramificare il codice. E la biblioteca Promesse per Kotlin chiamata Kovenant fa lo stesso sotto forma di promesse.
per le raccolte come tipi di restituzione restituisce sempre una raccolta vuota anziché una null
, a meno che non sia necessario un terzo stato di "non presente". Kotlin ha funzioni di supporto come emptyList()
oemptySet()
per creare questi valori vuoti.
quando si utilizzano metodi che restituiscono un valore nullable per il quale si dispone di un valore predefinito o alternativo, utilizzare l'operatore Elvis per fornire un valore predefinito. Nel caso di un Map
utilizzo, getOrElse()
ciò consente di generare un valore predefinito invece del Map
metodo get()
che restituisce un valore nullable. Lo stesso pergetOrPut()
quando si sovrascrivono metodi da Java in cui Kotlin non è sicuro della nullità del codice Java, è sempre possibile eliminare la ?
nullità dalla sostituzione se si è sicuri di quale firma e funzionalità dovrebbero essere. Pertanto il tuo metodo ignorato è più null
sicuro. Lo stesso per l'implementazione delle interfacce Java in Kotlin, modifica la nullità per essere ciò che sai essere valido.
guarda le funzioni che possono già aiutare, come for String?.isNullOrEmpty()
e String?.isNullOrBlank()
che possono operare su un valore nullable in modo sicuro e fare ciò che ti aspetti. In effetti, puoi aggiungere le tue estensioni per colmare eventuali lacune nella libreria standard.
l'asserzione funziona come checkNotNull()
e requireNotNull()
nella libreria standard.
funzioni di supporto come quelle filterNotNull()
che rimuovono i null dagli insiemi o listOfNotNull()
per restituire uno zero o un singolo elenco di elementi da un possibile null
valore.
esiste anche un operatore di cast sicuro (nullable) che consente a un cast di restituire un tipo non nullable null se non è possibile. Ma non ho un caso d'uso valido per questo che non è risolto dagli altri metodi sopra menzionati.