Основные типы
В Kotlin всё является объектом в том смысле, что мы можем вызывать методы и свойства для любой переменной. Некоторые типы могут иметь специальное внутреннее представление — например, числа, символы и булевы значения могут представляться примитивными значениями во время выполнения — но для пользователя они выглядят как обычные классы. В этом разделе мы описываем основные типы, используемые в Kotlin: числа, булевы значения, символы, строки и массивы.
Числа
Целочисленные типы
Kotlin предоставляет набор встроенных типов, представляющих числа.
Для целых чисел существуют четыре типа с различными размерами и, следовательно, диапазонами значений.
Тип |
Размер (биты) |
Минимальное значение |
Максимальное значение |
|---|---|---|---|
|
8 |
-128 |
127 |
|
16 |
-32768 |
32767 |
|
32 |
-2 147 483 648 (-231) |
2 147 483 647 (231 - 1) |
|
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 обеспечивает двойную точность.
Тип |
Размер (биты) |
Значимые биты |
Биты порядка |
Десятичные знаки |
|---|---|---|---|---|
|
32 |
24 |
8 |
6-7 |
|
64 |
53 |
11 |
15-16 |
Вы можете инициализировать переменные типа Double и Float числами с дробной частью. Она отделена от целой части точкой (.) Для переменных, инициализированных дробными числами, компилятор определяет тип Double.
val pi = 3.14 // Double // val one: Double = 1 // Error: type mismatch val oneDouble = 1.0 // Double
Чтобы явно указать тип Float для значения, добавьте суффикс f или F. Если такое значение содержит более 6-7 десятичных знаков, оно будет округлено.
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.0
val f = 1.0f
printDouble(d)
// printDouble(i) // Error: Type mismatch
// printDouble(f) // Error: Type mismatch
}
Для преобразования числовых значений в разные типы используйте явные преобразования.
Литеральные константы
Существуют следующие виды литеральных констант для целочисленных значений:
-
Десятичные:
123Длинные числа отмечаются заглавной
L:123L
Шестнадцатеричные:
0x0FДвоичные:
0b00001011
Kotlin также поддерживает стандартную запись для чисел с плавающей точкой:
Вещественные числа по умолчанию:
123.5,123.5e10Числа с плавающей точкой типа float отмечаются
fилиF:123.5f
Можно использовать нижние подчеркивания для повышения читаемости числовых констант:
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
Представление чисел на JVM
На платформе JVM числа хранятся как примитивные типы: int, double, и так далее. Исключением являются случаи, когда вы создаете ссылку на число со значением null, например Int?, или используете обобщения. В этих случаях числа упаковываются в классы Java Integer, Double, и так далее.
Обратите внимание, что ссылки на одно и то же число со значением null могут быть разными объектами:
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
}
Все ссылки на null-значения a на самом деле являются одним и тем же объектом из-за оптимизации памяти, применяемой JVM для Integer между -128 и 127. Она не применяется к ссылкам b, поэтому они являются разными объектами.
С другой стороны, они всё ещё равны:
fun main() {
//sampleStart
val b: Int = 10000
println(b == b) // Prints 'true'
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB == anotherBoxedB) // 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
val i1: Int = b.toInt()
//sampleEnd
}
Все числовые типы поддерживают преобразования в другие типы:
toByte(): BytetoShort(): ShorttoInt(): InttoLong(): LongtoFloat(): FloattoDouble(): DoubletoChar(): Char
Во многих случаях явные преобразования не нужны, так как тип определяется из контекста, а арифметические операции перегружены для соответствующих преобразований, например:
val l = 1L + 3 // Long + Int => Long
Операции
Kotlin поддерживает стандартный набор арифметических операций над числами: +, -, *, /, %. Они объявлены как члены соответствующих классов.
fun main() {
//sampleStart
println(1 + 2)
println(2_500_000_000L - 1L)
println(3.14 * 2.71)
println(10.0 / 3)
//sampleEnd
}
Вы также можете переопределить эти операторы для пользовательских классов. См. Перегрузку операторов для получения подробной информации.
Деление целых чисел
Деление целых чисел всегда возвращает целое число. Любая дробная часть отбрасывается.
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
}
Битовые операции
Kotlin предоставляет набор битовых операций над целочисленными числами. Они работают на битовом уровне напрямую с битами представления чисел. Битовые операции представлены функциями, которые могут вызываться в инфиксной форме. Они могут быть применены только к Int и Long.
val x = (1 shl 2) and 0x000FF000
Вот полный список битовых операций:
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
Целые без знака
В дополнение к целым типам, Kotlin предоставляет следующие типы для целых чисел без знака:
UByte: целое без знака 8 бит, диапазон от 0 до 255UShort: целое без знака 16 бит, диапазон от 0 до 65535UInt: целое без знака 32 бит, диапазон от 0 до 2^32 - 1ULong: целое без знака 64 бит, диапазон от 0 до 2^64 - 1
Типы без знака поддерживают большинство операций своих знаковых аналогов.
Массивы и диапазоны без знака
Как и с примитивами, каждый тип без знака имеет соответствующий тип, представляющий массивы этого типа:
UByteArray: массив байтов без знакаUShortArray: массив коротких целых без знакаUIntArray: массив целых без знакаULongArray: массив длинных целых без знака
Как и массивы знаковых целых чисел, они предоставляют API, аналогичный классу Array, без накладных расходов на упаковку.
При использовании массивов без знака вы получите предупреждение, указывающее, что эта функция еще не является стабильной. Чтобы удалить предупреждение, включите ее с помощью аннотации @ExperimentalUnsignedTypes. Решение о том, должны ли ваши клиенты явно включить использование вашего API, зависит от вас, но помните, что массивы без знака не являются стабильной функцией, поэтому API, использующий их, может быть сломан изменениями в языке. Узнайте больше о требованиях к включению.
Диапазоны и прогрессии поддерживаются для UInt и ULong классами UIntRange, UIntProgression, ULongRange, и 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
Дополнительное обсуждение
См. предложение языка для типов без знака для технических деталей и дополнительного обсуждения.
Булевы
Тип Boolean представляет булевы объекты, которые могут иметь два значения: true и false.
Boolean имеет nullable аналог Boolean?, который также имеет значение null
Встроенные операции с булевыми значениями включают:
||— дизъюнкция (логическое ИЛИ)&&— конъюнкция (логическое И)!— отрицание (логическое НЕ)
|| и && работают лениво.
fun main() {
//sampleStart
val myTrue: Boolean = true
val myFalse: Boolean = false
val boolNull: Boolean? = null
println(myTrue || myFalse)
println(myTrue && myFalse)
println(!myTrue)
//sampleEnd
}
Символы
Символы представлены типом Char. Литералы символов заключаются в одинарные кавычки: '1'
Специальные символы начинаются с обратной косой черты \. Поддерживаются следующие последовательности экранирования: \t, \b, \n, \r, \', \", \\ и \$.
Для кодирования любого другого символа используйте синтаксис Unicode-экранирования: '\uFF00'.
fun main() {
//sampleStart
val aChar: Char = 'a'
println(aChar)
println('\n') //prints an extra newline character
println('\uFF00')
//sampleEnd
}
Если значение символьной переменной — это цифра, вы можете явно преобразовать ее в число Int с помощью функции digitToInt().
Строки
Строки в Kotlin представлены типом String. В общем случае, строковое значение представляет собой последовательность символов в двойных кавычках (").
val str = "abcd 123"
Элементы строки — это символы, к которым можно получить доступ с помощью операции индексирования: s[i]. Вы можете перебирать эти символы с помощью цикла for:
fun main() {
val str = "abcd"
//sampleStart
for (c in str) {
println(c)
}
//sampleEnd
}
Строки неизменяемы. После инициализации строки вы не можете изменить её значение или присвоить новое значение. Все операции, которые преобразуют строки, возвращают результаты в новом String объекте, оставляя исходную строку неизменной.
fun main() {
//sampleStart
val str = "abcd"
println(str.uppercase()) // Create and print a new String object
println(str) // the original string remains the same
//sampleEnd
}
Для конкатенации строк используйте оператор +. Это также работает для конкатенации строк со значениями других типов, если первый элемент в выражении — строка:
fun main() {
//sampleStart
val s = "abc" + 1
println(s + "def")
//sampleEnd
}
Обратите внимание, что в большинстве случаев предпочтительнее использовать шаблоны строк или сырые строки, чем конкатенацию строк.
Литералы строк
Kotlin имеет два типа литералов строк:
Экранированные строки, которые могут содержать экранированные символы
Сырые строки, которые могут содержать символы новой строки и произвольный текст
Вот пример экранированной строки:
val s = "Hello, world!\n"
Экранирование выполняется стандартным способом, с помощью обратной косой черты (\). Список поддерживаемых последовательностей escape-символов см. выше в разделе Символы.
Сырая строка ограничена тройной кавычкой ("""). Она не поддерживает экранирование и может содержать символы новой строки и любые другие символы:
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
"""
Массивы
Массивы в 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() может быть использована для создания массива заданного размера, заполненного null элементами.
Другой вариант — использовать конструктор 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 }
© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/basic-types.html