Поскольку Kotlin — статически типизированный язык, полностью совместимый с Java, он имеет такую же систему типов, как и Java. Идя дальше, Kotlin привнес несколько улучшений, сделав его более последовательным, элегантным, практичным и не зависящим от платформы языком. Давайте рассмотрим некоторые из этих особенностей, чтобы лучше понять, что делает Kotlin таким привлекательным.
Any
Аналогичен классу Object в Java, который является корневым типом каждого объекта, но между ними есть тонкие различия. Как следует из именования в Java, он работает только с объектными типами, но не с примитивными типами, такими как int
, char
и т.д. Конечно, это связано с некоторым низкоуровневым управлением памятью и тем, где должна храниться переменная, и мы можем получить больше производительности, если будем использовать только примативные типы. Но Kotlin лучше работает даже здесь, он автоматически преобразует к примитивным типам Java, где это возможно, и устраняет необходимость объявлять примитивные типы специально с точки зрения языка программирования высокого уровня. Это решение делает его более последовательным, а также отделяет его от реализации Java, чтобы впоследствии использовать Kotlin на множестве платформ.
public open class Any { public open operator fun equals(other: Any?): Boolean public open fun hashCode(): Int public open fun toString(): String }
Когда дело доходит до специфических для Java реализаций, от которых Kotlin не может избавиться, он иногда использует функции расширения Kotlin и помещает эти расширения в отдельный пакет. Вы можете проверить, как функция Java getClass()
реализована в Kotlin, а также проверить, насколько она проста/элегантна, как определение Any
в Kotlin выше.
Optional
Поскольку NullPointerEexception(NPE) может стать миллиардной ошибкой Java, с которой, я думаю, сталкивается каждый Java-разработчик, Kotlin определенно не хочет идти по тому же пути.
Ответ на этот вопрос также довольно распространен в наши дни — Nullability. Каждый тип в Kotlin будет иметь версию nulltable, и, объявив nullability специально, мы можем быть уверены в том, что нам нужно или не нужно делать проверку на null, и навсегда избавиться от NPE благодаря компилятору Kotlin.
Есть один тонкий фактор, который мы используем каждый день, но о котором, возможно, не знаем. Типы NonNull и Nullable принадлежат к разным группам, и только тип Nullable может перейти к типу NonNull, доказав свою нулевость, неважно, с помощью проверки на нулевость или принудительного разворачивания. Таким образом, каждый NonNull/Nullable тип остается в своей собственной группе постоянно, что обеспечивает безопасность.
Хотя в собственном мире Kotlin работать с null безопасно, вам может быть интересно узнать, как использовать Kotlin вместе с Java. За дополнительной информацией обращайтесь на сайт с официальной документацией.
Nothing
А вот и самая интересная часть системы типов Kotlin с моей точки зрения — Nothing. Давайте сначала посмотрим исходный код:
public class Nothing private constructor()
Даже если это всего лишь одна строчка кода, она говорит о многом.
- Во-первых, она не открыта, поэтому никто не может ее расширить.
- Во-вторых, конструктор является приватным, и он не выставляет ничего, чтобы инстатировать себя.
- И, наконец, нет ни одной функции, которую бы он предоставлял.
Казалось бы, не самый полезный тип, верно? Даже если вы не разбираетесь в Nothing, вы можете без проблем написать код без ошибок. Однако если вы углубитесь в документацию или комментарии к Nothing, то вскоре обнаружите, что он функционирует как нижний тип для всех остальных типов, противоположный Any.
Зачем нам нужен нижний тип для чего-либо?Разве он не будет подключен, если он является дочерним типом String и Int? Да, пересечение всего — это то, что не может существовать на самом деле, и это как раз и есть определение Nothing
.
Давайте попробуем подробнее разобраться в классическом примере, как сказано в документе, функция имеет возвращаемый тип Nothing, это означает, что она никогда не возвращает результат (всегда генерирует исключение).
interface Animal { fun eat(): String fun walk(): String } class Dog : Animal { override fun eat() = "dog eat" override fun walk() = "dog walk" }
Давайте определим тип Animal
с двумя классическими функциями, обе из которых возвращают строку для отображения действия. Что, если мы хотим создать Fish
, который является продолжением Animal
?
class Fish : Animal { override fun eat() = "fish eat" override fun walk() = throw RuntimeException("fish can't walk") }
Поскольку рыба не может ходить, мы бы хотели вместо этого создать исключение, и если вы вызовете функцию walk
, как показано ниже, вы увидите интересное предупреждение.
fun main() { val fish = Fish() fish.walk() println("where am I") // IDE warn this will be unreachable code }
И если вы назначите тип рыбы для Animal
, предупреждение исчезнет. Если вы проверите возвращаемый тип рыбной функции walk
, вы обнаружите, что он автоматически меняется со String
на Nothing
. Поскольку Nothing
не может существовать, функция с типом возвращаемого значения Nothing
не может успешно вернуть значение, поэтому IDE может знать, что println
недоступен. И поскольку throw может произойти где угодно, у каждого типа будет дочерний тип Nothing
. Следовательно, IDE может работать лучше, а сам язык может иметь лучший дизайн, который является более последовательным.
Nothing
не ограничивает возможность генерирования исключений, функция с бесконечным циклом также является допустимым случаем. Функция TODO()
также ничего не вернет, поскольку она не должна быть успешно выполнена. И если вы поищете использование Nothing
в самом Kotlin, вы, возможно, обнаружите, что тип также используется для представления любой пустой коллекции, поскольку Nothing
может быть общим дочерним элементом для всего. Хотя на самом деле вы не можете его использовать, но оно служит хорошим заполнителем, который вы можете заменить позже. Возьмем, к примеру, listOf
:
var list: List<String> = listOf() // Kotlin's Collections.kt public inline fun <T> listOf(): List<T> = emptyList() public fun <T> emptyList(): List<T> = EmptyList internal object EmptyList : List<Nothing>
Бонус
Можете ли вы попытаться определить, к какому именно типу относится myObj
, выведенный ниже? (Ответ: можно вставить в IDE и попытаться переназначить его):
var myObj = null
На сегодня это все. С Any
в качестве верхнего типа, Nothing
в качестве нижнего типа и Optional
в стороне, я надеюсь, что вы так же найдете систему типов Kotlin приятной и красивой.