По-перше, ви повинні прочитати все про 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значення.
є також безпечний (нульовий) оператор лиття , який дозволяє, якщо це неможливо, повернути нульовий тип повернення до нульового типу. Але у мене немає вагомого випадку використання, який не вирішується іншими методами, згаданими вище.