Основные типы
В Kotlin всё является объектом в том смысле, что мы можем вызывать методы и свойства для любой переменной. Некоторые типы могут иметь специальное внутреннее представление — например, числа, символы и булевы значения могут быть представлены как примитивные значения во время выполнения — но для пользователя они выглядят как обычные классы. В этом разделе мы описываем основные типы, используемые в Kotlin: числа, символы, булевы значения, массивы и строки.
Числа
Kotlin предоставляет набор встроенных типов для представления чисел.
Для целых чисел существуют четыре типа с различными размерами и, следовательно, диапазонами значений.
| Тип | Размер (бит) | Минимальное значение | Максимальное значение |
|---|---|---|---|
| Byte | 8 | -128 | 127 |
| Short | 16 | -32768 | 32767 |
| Int | 32 | -2 147 483 648 (-231) | 2 147 483 647 (231 - 1) |
| Long | 64 | -9 223 372 036 854 775 808 (-263) | 9 223 372 036 854 775 807 (263 - 1) |
Все переменные, инициализированные целыми значениями, не превышающими максимальное значение Int, имеют тип Int по умолчанию. Если начальное значение превышает это значение, то тип является Long. Чтобы явно указать тип Long, добавьте суффикс L к значению.
val one = 1 // Int val threeBillion = 3000000000 // Long val oneLong = 1L // Long val oneByte: Byte = 1
Для чисел с плавающей точкой Kotlin предоставляет типы Float и Double. Согласно стандарту IEEE 754, типы чисел с плавающей точкой отличаются по своей точности, то есть по количеству десятичных знаков, которые они могут хранить. Float отражает одинарную точность IEEE 754, в то время как Double обеспечивает двойную точность.
| Тип | Размер (бит) | Значимые биты | Биты экспоненты | Десятичные знаки |
|---|---|---|---|---|
| Float | 32 | 24 | 8 | 6-7 |
| Double | 64 | 53 | 11 | 15-16 |
Для переменных, инициализированных дробными числами, компилятор определяет тип Double по умолчанию. Чтобы явно указать тип Float для значения, добавьте суффикс f или F. Если такое значение содержит более 6-7 десятичных знаков, оно будет округлено.
val pi = 3.14 // Double val e = 2.7182818284 // Double val eFloat = 2.7182818284f // Float, actual value is 2.7182817
Обратите внимание, что в отличие от некоторых других языков, в Kotlin нет неявных расширений типов для чисел. Например, функцию с параметром типа Double можно вызвать только с значениями типа Double, а не Float, Int или другими числовыми значениями.
fun main() {
fun printDouble(d: Double) { print(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
// printDouble(i) // Error: Type mismatch
// printDouble(f) // Error: Type mismatch
}
Для преобразования числовых значений в разные типы используйте явные преобразования.
Литеральные константы
Существуют следующие виды литеральных констант для целочисленных значений:
- Десятичные:
123- Long'ы помечаются заглавной буквой
L:123L
- Long'ы помечаются заглавной буквой
- Шестнадцатеричные:
0x0F - Двоичные:
0b00001011
ПРИМЕЧАНИЕ: Восьмеричные литералы не поддерживаются.
Kotlin также поддерживает обычную запись для чисел с плавающей точкой:
- Double по умолчанию:
123.5,123.5e10 - Float помечается
fилиF:123.5f
Подчёркивания в числовых литералах (с версии 1.1)
Вы можете использовать подчёркивания для повышения читабельности констант чисел:
val oneMillion = 1_000_000 val creditCardNumber = 1234_5678_9012_3456L val socialSecurityNumber = 999_99_9999L val hexBytes = 0xFF_EC_DE_5E val bytes = 0b11010010_01101001_10010100_10010010
Представление
На платформе Java числа физически хранятся как примитивные типы JVM, если нам не нужен ссылочный тип на число (например, Int?) или используются дженерики. В последних случаях числа упаковываются.
Обратите внимание, что упаковка чисел не обязательно сохраняет идентичность:
fun main() {
//sampleStart
val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
val b: Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedA === anotherBoxedA) // true
println(boxedB === anotherBoxedB) // false
//sampleEnd
}
С другой стороны, она сохраняет равенство:
fun main() {
//sampleStart
val a: Int = 10000
println(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
println(boxedA == anotherBoxedA) // Prints 'true'
//sampleEnd
}
Явные преобразования
Из-за разных представлений меньшие типы не являются подтипами больших. Если бы они были, у нас возникли бы проблемы следующего рода:
// Hypothetical code, does not actually compile: val a: Int? = 1 // A boxed Int (java.lang.Integer) val b: Long? = a // implicit conversion yields a boxed Long (java.lang.Long) print(b == a) // Surprise! This prints "false" as Long's equals() checks whether the other is Long as well
Поэтому равенство было бы потеряно беззвучно повсюду, не говоря уже об идентичности.
Следовательно, меньшие типы НЕ неявно преобразуются в большие типы. Это означает, что мы не можем присвоить значение типа Byte переменной типа Int без явного преобразования.
fun main() {
//sampleStart
val b: Byte = 1 // OK, literals are checked statically
val i: Int = b // ERROR
//sampleEnd
}
Мы можем использовать явные преобразования для расширения диапазона чисел.
fun main() {
val b: Byte = 1
//sampleStart
val i: Int = b.toInt() // OK: explicitly widened
print(i)
//sampleEnd
}
Каждый числовой тип поддерживает следующие преобразования:
toByte(): BytetoShort(): ShorttoInt(): InttoLong(): LongtoFloat(): FloattoDouble(): DoubletoChar(): Char
Отсутствие неявных преобразований редко заметно, потому что тип определяется из контекста, а арифметические операции перегружены для соответствующих преобразований, например
val l = 1L + 3 // Long + Int => Long
Операции
Kotlin поддерживает стандартный набор арифметических операций над числами (+ - * / %), которые объявляются как члены соответствующих классов (но компилятор оптимизирует вызовы до соответствующих инструкций). См. Перегрузку операторов.
Деление целых чисел
Обратите внимание, что деление целых чисел всегда возвращает целое число. Любая дробная часть отбрасывается. Например:
fun main() {
//sampleStart
val x = 5 / 2
//println(x == 2.5) // ERROR: Operator '==' cannot be applied to 'Int' and 'Double'
println(x == 2)
//sampleEnd
}
Это верно для деления между любыми двумя целочисленными типами.
fun main() {
//sampleStart
val x = 5L / 2
println(x == 2L)
//sampleEnd
}
Для возвращения типа с плавающей точкой явно преобразуйте один из аргументов в тип с плавающей точкой.
fun main() {
//sampleStart
val x = 5 / 2.toDouble()
println(x == 2.5)
//sampleEnd
}
Битовые операции
Что касается битовых операций, то нет специальных символов, а только именованные функции, которые можно вызывать в инфиксной форме, например:
val x = (1 shl 2) and 0x000FF000
Вот полный список битовых операций (доступных только для Int и Long):
-
shl(bits)– сдвиг влево со знаком -
shr(bits)– сдвиг вправо со знаком -
ushr(bits)– сдвиг вправо без знака -
and(bits)– побитовое и -
or(bits)– побитовое или -
xor(bits)– побитовое исключающее или -
inv()– инверсия битов
Сравнение чисел с плавающей точкой
Операции с числами с плавающей точкой, обсуждаемые в этом разделе:
- Проверки на равенство:
a == bиa != b - Операторы сравнения:
a < b,a > b,a <= b,a >= b - Создание диапазонов и проверки на вхождение в диапазон:
a..b,x in a..b,x !in a..b
Когда операнды a и b статически известны как Float или Double или их nullable аналоги (тип объявлен или выведен или является результатом умного преобразования), операции над числами и диапазоном, который они образуют, следуют стандарту IEEE 754 для арифметики с плавающей точкой.
Однако, чтобы поддержать общие случаи использования и обеспечить полное упорядочение, когда операнды не статически типизированы как числа с плавающей точкой (например, Any, Comparable<...>, параметр типа), операции используют реализации equals и compareTo для Float и Double, которые не согласуются со стандартом, так что:
-
NaNсчитается равным самому себе -
NaNсчитается больше любого другого элемента, включаяPOSITIVE_INFINITY -
-0.0считается меньше0.0
Символы
Символы представлены типом Char. Их нельзя непосредственно использовать как числа.
fun check(c: Char) {
if (c == 1) { // ERROR: incompatible types
// ...
}
}
Литералы символов заключаются в одинарные кавычки: '1'. Специальные символы могут быть экранированы с помощью обратной косой черты. Поддерживаются следующие последовательности экранирования: \t, \b, \n, \r, \', \", \\ и \$. Для кодирования любого другого символа используйте синтаксис Unicode-экранирования: '\uFF00'.
Мы можем явно преобразовать символ в число типа Int:
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}
Как и числа, символы упаковываются, когда требуется nullable ссылка. Идентичность не сохраняется операцией упаковки.
Булевы значения
Тип Boolean представляет булевы значения и имеет два значения: true и false.
Булевы значения упаковываются, если требуется nullable ссылка.
Встроенные операции с булевыми значениями включают
-
||– ленивое дизъюнкция -
&&– ленивое конъюнкция -
!- отрицание
Массивы
Массивы в Kotlin представлены классом Array, который имеет функции get и set (преобразуемые в [] через соглашения по перегрузке операторов) и свойство size, а также несколько других полезных функций-членов:
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ...
}
Для создания массива можно использовать библиотечную функцию arrayOf() и передавать в неё значения элементов, так что arrayOf(1, 2, 3) создаёт массив [1, 2, 3]. В качестве альтернативы можно использовать библиотечную функцию arrayOfNulls() для создания массива заданного размера, заполненного нулевыми элементами.
Другой вариант — использовать конструктор Array, который принимает размер массива и функцию, которая может вернуть начальное значение каждого элемента массива, заданного его индексом:
fun main() {
//sampleStart
// Creates an Array<String> with values ["0", "1", "4", "9", "16"]
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { println(it) }
//sampleEnd
}
Как было сказано выше, операция [] обозначает вызовы функций-членов get() и set().
Массивы в Kotlin являются неизменяемыми. Это означает, что Kotlin не позволяет нам присвоить массив Array<String> массиву Array<Any>, что предотвращает возможную ошибку во время выполнения (но вы можете использовать Array<out Any>, см. Проекции типов).
Массивы примитивных типов
Kotlin также имеет специализированные классы для представления массивов примитивных типов без избыточной упаковки: ByteArray, ShortArray, IntArray и так далее. Эти классы не имеют отношения к наследованию класса Array, но имеют тот же набор методов и свойств. Каждый из них также имеет соответствующую функцию-фабрику:
val x: IntArray = intArrayOf(1, 2, 3) x[0] = x[1] + x[2]
// Array of int of size 5 with values [0, 0, 0, 0, 0]
val arr = IntArray(5)
// e.g. initialise the values in the array with a constant
// Array of int of size 5 with values [42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 }
// e.g. initialise the values in the array using a lambda
// Array of int of size 5 with values [0, 1, 2, 3, 4] (values initialised to their index value)
var arr = IntArray(5) { it * 1 }
Беззнаковые целые числа
Беззнаковые типы доступны только начиная с Kotlin 1.3 и в настоящее время находятся в Бета-версии. Подробности см. ниже
Kotlin вводит следующие типы беззнаковых целых чисел:
-
kotlin.UByte: беззнаковый 8-битный целое число, диапазон от 0 до 255 -
kotlin.UShort: беззнаковый 16-битный целое число, диапазон от 0 до 65535 -
kotlin.UInt: беззнаковый 32-битный целое число, диапазон от 0 до 2^32 - 1 -
kotlin.ULong: беззнаковый 64-битный целое число, диапазон от 0 до 2^64 - 1
Беззнаковые типы поддерживают большинство операций своих знакомых аналогов.
Обратите внимание, что изменение типа с беззнакового на знаковый (и наоборот) является несовместимым изменением
Беззнаковые типы реализованы с помощью другой ещё не стабильной функции, а именно встроенных классов.
Специализированные классы
Так же, как и для примитивов, каждый из беззнаковых типов имеет соответствующий тип, представляющий массив, специализированный для этого беззнакового типа:
-
kotlin.UByteArray: массив беззнаковых байтов -
kotlin.UShortArray: массив беззнаковых целых чисел (short) -
kotlin.UIntArray: массив беззнаковых целых чисел (int) -
kotlin.ULongArray: массив беззнаковых целых чисел (long)
Так же, как и для массивов знаковых целых чисел, они предоставляют аналогичный API классу Array без избыточной упаковки.
Также, диапазоны и последовательности поддерживаются для UInt и ULong классами kotlin.ranges.UIntRange, kotlin.ranges.UIntProgression, kotlin.ranges.ULongRange, kotlin.ranges.ULongProgression
Литералы
Для упрощения использования беззнаковых целых чисел Kotlin предоставляет возможность пометить целочисленный литерал суффиксом, указывающим конкретный беззнаковый тип (аналогично Float/Long):
- суффиксы
uиUпомечают литерал как беззнаковый. Точный тип будет определён на основе ожидаемого типа. Если ожидаемый тип не указан, будет выбранUIntилиULongв зависимости от размера литерала
val b: UByte = 1u // UByte, expected type provided val s: UShort = 1u // UShort, expected type provided val l: ULong = 1u // ULong, expected type provided val a1 = 42u // UInt: no expected type provided, constant fits in UInt val a2 = 0xFFFF_FFFF_FFFFu // ULong: no expected type provided, constant doesn't fit in UInt
- суффиксы
uLиULявно помечают литерал как беззнаковое long.
val a = 1UL // ULong, even though no expected type provided and constant fits into UInt
Статус бета-версии беззнаковых целых чисел
Конструкции беззнаковых типов находятся в Бета-версии, что означает, что их совместимость — лишь наилучшее усилие и не гарантируется. При использовании беззнаковой арифметики в Kotlin 1.3+ будет выдаваться предупреждение, указывающее, что эта функция не выпущена в стабильной форме. Чтобы удалить предупреждение, нужно включить использование беззнаковых типов.
Существует два способа включения беззнаковых типов: с требованием включения для вашего API или без него.
- Для распространения требования включения используйте аннотацию
@ExperimentalUnsignedTypesдля объявлений, использующих беззнаковые целые числа. - Для включения без распространения используйте аннотацию
@OptIn(ExperimentalUnsignedTypes::class)или передайте-Xopt-in=kotlin.ExperimentalUnsignedTypesкомпилятору.
Вы сами решаете, должны ли ваши клиенты явно включать использование вашего API, но имейте в виду, что беззнаковые типы не являются стабильной функцией, поэтому API, использующий их, может быть нарушен изменениями языка.
См. также требования к включению API KEEP для технических подробностей.
Дополнительное обсуждение
См. предложение языка по беззнаковым типам для технических подробностей и дальнейшего обсуждения.
Строки
Строки представлены типом String. Строки неизменяемы. Элементы строки — это символы, к которым можно получить доступ с помощью операции индексирования: s[i]. Строку можно перебирать с помощью цикла for:
fun main() {
val str = "abcd"
//sampleStart
for (c in str) {
println(c)
}
//sampleEnd
}
Вы можете конкатенировать строки, используя оператор +. Это также работает для конкатенации строк со значениями других типов, если первый элемент в выражении является строкой:
fun main() {
//sampleStart
val s = "abc" + 1
println(s + "def")
//sampleEnd
}
Обратите внимание, что в большинстве случаев предпочтительнее использовать шаблоны строк или сырые строки, чем конкатенацию строк.
Литералы строк
Kotlin имеет два типа литералов строк: экранированные строки, которые могут содержать экранированные символы, и сырые строки, которые могут содержать символы перевода строки и произвольный текст. Вот пример экранированной строки:
val s = "Hello, world!\n"
Экранирование выполняется обычным способом с помощью обратной косой черты. См. Символы выше для списка поддерживаемых последовательностей экранирования.
Сырая строка ограничена тройной кавычкой ("""), не содержит экранирования и может содержать символы перевода строки и любые другие символы:
val text = """
for (c in "foo")
print(c)
"""
Вы можете удалить ведущие пробелы с помощью функции trimMargin():
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
По умолчанию | используется как префикс отступа, но вы можете выбрать другой символ и передать его в качестве параметра, как в trimMargin(">").
Шаблоны строк
Литералы строк могут содержать выражения шаблонов, т.е. фрагменты кода, которые оцениваются и результаты которых конкатенируются в строку. Выражение шаблона начинается с символа доллара ($) и состоит либо из простого имени:
fun main() {
//sampleStart
val i = 10
println("i = $i") // prints "i = 10"
//sampleEnd
}
или произвольного выражения в фигурных скобках:
fun main() {
//sampleStart
val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"
//sampleEnd
}
Шаблоны поддерживаются как в обычных строках, так и в экранированных. Если вам нужно представить буквальный $ символ в обычной строке (которая не поддерживает экранирование обратной косой чертой), вы можете использовать следующий синтаксис:
val price = """
${'$'}9.99
"""
© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/basic-types.html