Українська
Приведення типів, Any та ініціалізація
Перевірка типу і безпечне приведення
is перевіряє, чи об’єкт належить типу; після відповідної умови компілятор часто застосовує smart cast. Для локального val це особливо передбачувано: посилання не може змінитися між перевіркою та використанням. Для відкритої властивості або властивості з власним геттером гарантій може не бути; збережіть значення в локальній змінній.
as? повертає null, якщо приведення неможливе. Звичайний as у такій ситуації породжує виняток. Безпечне приведення доречне на межі, де дійсно надходять різнорідні об’єкти. В основному поліморфному алгоритмі велика кількість перевірок типу часто сигналізує, що спільний контракт неповний.
kotlin
interface Printable {
fun text(): String
}
class Note(private val value: String) : Printable {
override fun text(): String = value
}
fun inspect(value: Any) {
if (value is Printable) {
println("print: ${value.text()}")
}
val note = value as? Note
println(note?.text() ?: "not a Note")
}
fun main() {
inspect(Note("Hello"))
inspect(42)
}text
print: Hello
Hello
not a NoteТут Any свідомо допускає різні значення. Якщо функція завжди потребує Printable, краще оголосити саме цей параметр і перенести помилку на етап компіляції. Перевірка в рантаймі не повинна підміняти точний тип там, де він відомий.
Any: рівність, хеш і рядкове подання
Усі ненульові класи мають спільний корінь Any з методами equals, hashCode та toString. Any? також допускає null. Перевизначення toString допомагає діагностиці, але не визначає рівність об’єктів. https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any/.
equals має бути рефлексивним, симетричним, транзитивним і узгодженим за незмінних даних порівняння. Для ненульового об’єкта порівняння з null хибне. Якщо два об’єкти рівні, їхні хеші повинні бути однаковими; з однакового хешу рівність не випливає. Перевизначайте equals і hashCode разом.
Для сутності часто порівнюють стабільний ідентифікатор, для значення – усі змістовні компоненти. Не включайте в хеш поле, яке змінюється, поки об’єкт використовується як ключ словника чи елемент множини. Інакше пошук може перестати знаходити вже вставлений об’єкт. Колекції докладно в темі 10.
Наслідування ускладнює рівність: базовий клас може вважати два екземпляри рівними лише за id, а підклас додавати колір. Тоді симетрія може зламатися. Для простих типів-значень часто краще остаточний клас без підкласів або data-клас, ніж відкрита ієрархія з кількома правилами equals.
Ініціалізація та небезпечний відкритий виклик
Базова частина ініціалізується до властивостей підкласу. Якщо базовий init викликає відкритий метод, динамічний виклик може потрапити в підклас, стан якого ще не готовий. Навіть якщо приклад випадково працює, зміна підкласу може зламати його. Не викликайте відкриті члени в конструкторах, ініціалізаторах властивостей і init базового класу.
Наступна програма показує безпечний порядок: обидва конструктори лише повідомляють про власний етап, а поліморфний метод викликається після завершення створення.
kotlin
open class Base(val label: String) {
init { println("base: $label") }
open fun describe(): String = label
}
class Derived(label: String, private val suffix: String) :
Base(label) {
init { println("derived: $suffix") }
override fun describe(): String = "$label/$suffix"
}
fun main() {
val value: Base = Derived("A", "B")
println(value.describe())
}text
base: A
derived: B
A/BЯкщо потрібна складна побудова, фабрика може спочатку створити готовий об’єкт, а потім виконати явно названий етап. Фабрики в companion object розглядаються в темі 7. При цьому не слід видавати клієнту частково готовий екземпляр до завершення перевірок. Найпростіший надійний конструктор часто найкращий.