Дорогие читатели,
Помогите сделать документацию лучше!

К сожалению текст все еще содержит некоторое количество ошибок, и мы просим вас найти и исправить их. Если бы каждый из посетителей исправил хотя бы одну ошибку, то мы смогли бы довести до совершенства весь материал всего за один день.


Основные типы

В Kotlin всё является объектом, в том смысле, что пользователь может вызвать функцию или получить доступ к свойству любой переменной. Некоторые типы являются встроенными, т.к. их реализация оптимизирована, хотя для пользователя они могут выглядеть как обычные классы. В данном разделе описывается большинство этих типов: числа, символы, логические переменные и массивы.

Числа

Kotlin обрабатывает численные типы примерно так же, как и Java, хотя некоторые различия всё же присутствуют. Например, отсутствует неявное расширяющее преобразование для чисел, а литералы в некоторых случаях немного отличаются.

Для представления чисел в Kotlin используются следующие встроенные типы (подобные типам в Java):

Тип Количество бит
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

Обратите внимание, что символы (characters) в языке Kotlin не являются числами (в отличие от Java).

Символьные постоянные

В языке Kotlin присутствуют следующие виды символьных постоянных (констант) для целых значений:

  • Десятичные числа: 123
    • Тип Long обозначается заглавной L: 123L
  • Шестнадцатеричные числа: 0x0F
  • Двоичные числа: 0b00001011

ВНИМАНИЕ: Восьмеричные литералы не поддерживаются.

Также Kotlin поддерживает числа с плавающей запятой:

  • Тип Double по умолчанию: 123.5, 123.5e10
  • Тип Float обозначается с помощью f или F: 123.5f

Представление

Обычно платформа Java хранит числа в виде примитивных типов JVM; если же нам необходима ссылка, которая может принимать значение null (например, Int?), то используются обёртки. В приведённом ниже примере показано использование обёрток.

Обратите внимание, что использование обёрток для одного и того же числа не гарантирует равенства ссылок на них:

val a: Int = 10000
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

Однако, равенство по значению сохраняется:

val a: Int = 10000
print(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // Prints 'true'

Явные преобразования

Из-за разницы в представлениях меньшие типы не являются подтипами бОльших типов. В противном случае у нас возникли бы сложности:

// Возможный код, который на самом деле не скомпилируется:
val a: Int? = 1 // "Обёрнутый" Int (java.lang.Integer)
val b: Long? = a // неявное преобразование возвращает "обёрнутый" Long (java.lang.Long)
print(a == b) // Нежданчик! Данное выражение выведет "false" т. к. метод equals() типа Long предполагает, что вторая часть выражения также имеет тип Long

Таким образом, будет утрачена не только тождественность (равенство по ссылке), но и равенство по значению.

Как следствие, неявное преобразование меньших типов в большие НЕ происходит. Это значит, что мы не можем присвоить значение типа Byteпеременной типа Int без явного преобразования:

val b: Byte = 1 // порядок, литералы проверяются статически
val i: Int = b // ОШИБКА

Мы можем использовать явное преобразование для "расширения" чисел

val i: Int = b.toInt() // порядок: число явно расширено

Каждый численный тип поддерживает следующие преобразования:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char

Отсутствие неявного преобразования редко бросается в глаза, поскольку тип выводится из контекста, а арифметические действия перегружаются для подходящих преобразований, например:

val l = 1L + 3 // Long + Int => Long

Арифметические действия

Kotlin поддерживает обычный набор арифметических действий над числами, которые объявлены членами соответствующего класса (тем не менее, компилятор оптимизирует вызовы вплоть до соответствующих инструкций). См. Перегрузка операторов.

Что касается битовых операций, то вместо особых обозначений для них используются именованные функции, которые могут быть вызваны в инфиксной форме, к примеру:

val x = (1 shl 2) and 0x000FF000

Ниже приведён полный список битовых операций (доступны только для типов Int и Long):

  • shl(bits) – сдвиг влево с учётом знака (<< в Java)
  • shr(bits) – сдвиг вправо с учётом знака (>> в Java)
  • ushr(bits) – сдвиг вправо без учёта знака (>>> в Java)
  • and(bits) – побитовое И
  • or(bits) – побитовое ИЛИ
  • xor(bits) – побитовое исключающее ИЛИ
  • inv() – побитовое отрицание

Символы

Символы в Kotlin представлены типом Char. Напрямую они не могут рассматриваться в качестве чисел

fun check(c: Char) {
  if (c == 1) { // ОШИБКА: несовместимый тип
    // ...
  }
}

Символьные литералы записываются в одинарных кавычках: '1', '\n', '\uFF00'. Мы можем явно привести символ в число типа Int

fun decimalDigitValue(c: Char): Int {
  if (c !in '0'..'9')
    throw IllegalArgumentException("Вне диапазона")
  return c.toInt() - '0'.toInt() // Явные преобразования в число
}

Подобно числам, символы оборачиваются при необходимости использования nullable ссылки. При использовании обёрток тождественность (равенство по ссылке) не сохраняется.

Логический тип

Тип Boolean представляет логический тип данных и принимает два значения: true и false.

При необходимости использования nullable ссылок логические переменные оборачиваются.

Встроенные действия над логическими переменными включают

  • || – ленивое логическое ИЛИ
  • && – ленивое логическое И
  • ! - отрицание

Массивы

Массивы в Kotlin представлены классом Array, обладающим функциями get и set (которые обозначаются [] согласно соглашению о перегрузке операторов), и свойством size, а также несколькими полезными встроенными функциями:

class Array<T> private constructor() {
  val size: Int
  fun get(index: Int): T
  fun set(index: Int, value: T): Unit

  fun iterator(): Iterator<T>
  // ...
}

Для создания массива мы можем использовать библиотечную функцию arrayOf(), которой в качестве аргумента передаются элементы массива, т.е. выполнение arrayOf(1, 2, 3) создаёт массив [1, 2, 3]. С другой стороны библиотечная функция arrayOfNulls() может быть использована для создания массива заданного размера, заполненного значениями null.

Также для создания массива можно использовать фабричную функцию, которая принимает размер массива и функцию, возвращающую начальное значение каждого элемента по его индексу:

// создаёт массив типа Array<String> со значениями ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })

Как отмечено выше, оператор [] используется вместо вызовов встроенных функций get() и set().

Обратите внимание: в отличие от Java массивы в Kotlin являются неизменяемыми. Это значит, что Kotlin запрещает нам присваивать значение Array<String> массиву типа Array<Any>, предотвращая таким образом возможный отказ во время исполнения (хотя вы можете использовать Array<out Any>, см. Type Projections).

Также в Kotlin есть особые классы для представления массивов примитивных типов без дополнительных затрат на оборачивание: ByteArray, ShortArray, IntArray и т.д. Данные классы не наследуют класс Array, хотя и обладают тем же набором методов и свойств. У каждого из них есть соответствующая фабричная функция:

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

Строки

Строки в Kotlin представлены типом String. Строки являются неизменяемыми. Строки состоят из символов, которые могут быть получены по порядковому номеру: s[i]. Проход по строке выполняется циклом for:

for (c in str) {
  println(c)
}

Строковые литералы

В Kotlin представлены два типа строковых литералов: строки с экранированными символами и обычные строки, могущие содержать символы новой строки и произвольный текст. Экранированная строка очень похожа на строку в Java:

val s = "Hello, world!\n"

Экранирование выполняется общепринятым способом, а именно с помощью обратной косой черты.

Обычная строка выделена тройной кавычкой ("""), не содержит экранированных символов, но может содержать символы новой строки и любые другие символы:

val text = """
  for (c in "foo")
    print(c)
"""

Строковые шаблоны

Строки могут содержать шаблонные выражения, т.е. участки кода, которые выполняются, а полученный результат встраивается в строку. Шаблон начинается со знака доллара ($) и состоит либо из простого имени (например, переменной):

val i = 10
val s = "i = $i" // evaluates to "i = 10"

либо из произвольного выражения в фигурных скобках:

val s = "abc"
val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"

Шаблоны поддерживаются как в обычных, так и в экранированных строках. При необходимости символ $ может быть представлен с помощью следующего синтаксиса:

val price = "${'$'}9.99"