По-перше, ви повинні прочитати все про Null Safety в Котліні, яке ретельно висвітлює справи.
У Котлін, ви не можете отримати доступ до обнуляє значення , не будучи впевненим , що він не null
( Перевірка нульовий в умовах ), або стверджувати , що це, безумовно , НЕ null
використовуючи !!
вірний оператор , доступ до нього з ?.
безпечного виклику , або , нарешті , даючи що - то , можливо , null
значення за замовчуванням за допомогою ?:
оператора Elvis .
Для вашого першого випадку у вашому запитанні у вас є варіанти залежно від наміру коду, ви б використовували один із них, і всі вони ідіоматичні, але мають різні результати:
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()
}
У розділі "Чому це працює, коли нульова перевірка" читайте довідкову інформацію нижче на смарт-ролях .
Для вашого 2 - го випадку у вашому запитанні в питанні з Map
, якщо ви , як розробник впевнені результату не будучи null
, використовуйте !!
вірний оператор як твердження:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
або в іншому випадку, коли карта COULD повертає нуль, але ви можете надати значення за замовчуванням, тоді 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
чека, компілятор розумно передасть значення в тілі оператора, щоб не бути нульовим. Є деякі складні потоки, де цього не може статися, але для звичайних випадків це працює добре.
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
Або якщо ви робите is
перевірку на ненульовий тип:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
І те саме для виразів "коли", які також захищають:
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
перевірка не може бути інтелектуально введена в ненульове значення. Дивіться тему "Безпечні дзвінки" нижче для вирішення.
Інший випадок, якому не можна довіряти розумному ролику, щоб не мутувати, - це 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, якщо значення ліворуч є нульовим, інакше продовжує оцінювати вираз праворуч.
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
}
}
читати далі: Безпечні дзвінки
Більше про ?:
Оператора Елвіса
Оператор 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
Пов'язані теми
У Котліні більшість програм намагаються уникати null
значень, але це не завжди можливо. І іноді null
має ідеальний сенс. Деякі вказівки для роздумів:
в деяких випадках він гарантує різні типи повернення, які включають статус виклику методу та результат у разі успіху. Бібліотеки, такі як Результат, дають вам результат результату успіху чи відмови, який також може розгалужувати ваш код. І бібліотека Обіцянь для Котліна під назвою Ковенант робить те саме у вигляді обіцянок.
для колекцій як типів повернення завжди повертайте порожню колекцію замість а null
, якщо вам не потрібен третій стан "немає". Kotlin має допоміжні функції, такі як emptyList()
абоemptySet()
створювати ці порожні значення.
під час використання методів, що повертають нульове значення, для якого у вас є типовий або альтернативний варіант, використовуйте оператор Elvis, щоб вказати значення за замовчуванням. У разі Map
використання, getOrElse()
яке дозволяє генерувати значення за замовчуванням замість Map
методу, get()
який повертає нульове значення. Те саме дляgetOrPut()
коли переважні методи з Java, де Котлін не впевнений у нікчемності коду Java, ви завжди можете відмовитись ?
від зміни, якщо ви впевнені, якою має бути підпис та функціональність. Тому ваш перекритий метод є більш null
безпечним. Те саме, що реалізувати інтерфейси Java в Котліні, змінити нульовість таким, яким ви знаєте, що є дійсним.
подивіться на функції, які вже можуть допомогти, як, наприклад, String?.isNullOrEmpty()
і String?.isNullOrBlank()
які можуть безпечно працювати на нульовому значенні, і робити те, що ви очікуєте. Насправді ви можете додати власні розширення для заповнення будь-яких прогалин у стандартній бібліотеці.
Які стверджують функції , як checkNotNull()
і requireNotNull()
в стандартній бібліотеці.
допоміжні функції, як-от filterNotNull()
видалення нулів із колекцій, або listOfNotNull()
для повернення нуля або списку окремих елементів з можливого null
значення.
є також безпечний (нульовий) оператор лиття , який дозволяє, якщо це неможливо, повернути нульовий тип повернення до нульового типу. Але у мене немає вагомого випадку використання, який не вирішується іншими методами, згаданими вище.