Лямбды
Лямбды всегда находятся внутри фигурных скобок. Слева находятся аргументы, справа — тело функции. Разделяет их специальное выражение ->. Например, создадим выражение.
x + 5>
Открыли фигурную скобку, записали параметр в виде числа Int, а затем указали, что с ним нужно делать в правой части — прибавить к числу 5. Параметр может быть один, как в нашем пример, несколько или вообще не быть.
Лямбда-выражение можно сохранить в переменной, а затем обращаться к ней как к обычной функции. Можно вызвать лямбда-выражение при помощи invoke().
val lambda = < println("Hello Kitty!") >lambda.invoke()
Лямбда-выражение может послужить удобной заменой для паттернов Listener или Callback.
Примеры
// Нет аргументов и возвращает 1 < 1 >// () -> Int // Один аргумент в виде строки, который выводится на экран < s: String ->println(s) > // (String)->Unit // два аргумента типа Int и возвращает произведение чисел < a: Int, b: Int ->a * b > // (Int, Int)->Int
Код с лямбдами становится короче и читабельнее.
button.setOnClickListener < /* код для щелчка кнопки */ >
val sum = < x: Int, y: Int ->x + y > println(sum(1, 2)) // 3
Лямбды можно записывать в несколько строк.
val test = < a: Int, b: Int ->println("$a + $b") a + b >
Этот же пример можно записать одной строкой, разделяя команды точкой с запятой.
val test = < a: Int, b: Int ->println(«$a + $b»); a + b >
setOnClickListener
Рассмотрим применение лямбда-выражений на примере обработчика щелчка кнопки.
Код на Java 7 и ниже.
button.setOnClickListener(new OnClickListener() < @Override public void onClick(View view) < /* код для щелчка кнопки */ >>);
Если использовать такой же код на Kotlin, то получим.
button.setOnClickListener(object : View.OnClickListener < override fun onClick(v: View?) < println("Hello Kitty") >>)
Студия предложит сконвертировать код, используя лямбду. В моём случае студия сразу сконвертировала в самый короткий вариант. Но мы рассмотрим все случаи, например, возможен такой вариант.
button.setOnClickListener( < v ->println("Hello Kitty") >)
Если в последнем параметре функции используется функция, то её можно вынести за круглые скобки. Получится следующее.
button.setOnClickListener() < v ->println("Hello Kitty") >
Если у функции несколько параметров, то только последнюю можно вынести за круглые скобки.
А мы продолжаем уменьшать код. Если у функции только один параметр и он является функцией, то круглые скобки можно убрать.
button.setOnClickListener < v ->println("Hello Kitty") >
Если в самом лямбда-выражении не используется параметр в левой части, то его можно удалить. Это справедливо для выражения с одним параметром. В нашем примере v (View) не используется, сокращаем код ещё раз.
button.setOnClickListener
Функция с одним параметром. Ключевое слово it
Рассмотрим частный случай, когда функция только получает параметр. Мы можем не использовать левую часть, а использовать ключевое слово it. Например, мы используем v для его передачи другой функции.
button.setOnClickListener < v ->doSomething(v) > private fun doSomething(v: View?)
Убираем левую часть и используем it.
button.setOnClickListener
Все варианты в одном месте.
button.setOnClickListener( < v ->println("Hello Kitty") >) button.setOnClickListener() < v ->println("Hello Kitty") > button.setOnClickListener < v ->println("Hello Kitty") > button.setOnClickListener < println("Hello Kitty") >button.setOnClickListener < v ->doSomething(v) > button.setOnClickListener
Другой пример использования — пройтись по элементам коллекции. Например, воспользуемся forEach:
val cats = listOf("Barsik", "Murzik", "Ryzhik") cats.forEach < println(it) >// Выводит Barsik Murzik Ryzhik
Без использования ключевого слова it пришлось бы писать длинный вариант.
cats.forEach < cat ->println(cat) >
Функции высшего порядка
Функция, использующая лямбда-выражение для параметра или возвращаемого значения, называется функцией высшего порядка.
Напишем функцию convert(), которая преобразует значение Double по формуле, передаваемой в лямбда-выражении, выводит результат и возвращает её. Например, с помощью этой функции вы сможете преобразовать температуру по Цельсию в температуру по Фаренгейту или преобразовать вес из килограммов в фунты — все зависит от формулы, которая передаётся в лямбда-выражении (аргумента). Начнём с определения параметров функции.
Для этого в функцию будут добавлены два параметра: Double и лямбда-выражение. Назовём лямбда-выражение converter, а поскольку оно будет использоваться для преобразования Double в Double, оно должно иметь тип (Double) -> Double (лямбда-выражение, которое получает параметр Double и возвращает Double).
fun convert(x: Double, converter: (Double) -> Double) : Double < // ваш код >
Напишем какой-то для функции.
fun convert(x: Double, converter: (Double) -> Double) : Double < val result = converter(x) println("$x is converted to $result") // выводим результат return result // вернуть результат >
Функция с лямбда-параметром вызывается точно так же, как и любая другая функция: с передачей значений всех аргументов. В данном случае Double и лямбда-выражений.
Давайте используем функцию convert() для преобразования 20 градусов по Цельсию в градусы по Фаренгейту. Для этого функции нужно передать значения 20.0 и лямбда-выражение < c: Double ->c * 1.8 + 32 >:
convert(20.0, < c: Double ->c * 1.8 + 32 >) // 20.0 is converted to 68.0 // с присвоением возвращаемого результата переменной val fahrenheit = convert(20.0, < c: Double ->c * 1.8 + 32 >) println(fahrenheit)
Если последний параметр вызываемой функции является лямбда-выражением, лямбда-аргумент можно вынести за круглые скобки вызова функции.
// было convert(20.0, < c: Double ->c * 1.8 + 32 >) // стало convert(20.0) < c: Double ->c * 1.8 + 32 >
Если функция имеет всего один параметр, и этот параметр представляет собой лямбда-выражение, круглые скобки при вызове функции можно полностью опустить.
Предположим, функция convertFive() преобразует Int 5 в Double по формуле преобразования, которая передаётся в виде лямбда-выражения.
fun convertFive(converter: (Int) -> Double) : Double
// стандартный вариант convertFive() < it * 1.8 + 32 >// компактный вариант без круглых скобок convertFive
Функция может возвращать лямбда-выражение. Например, следующий код определяет функцию с именем getConversionLambda(), которая возвращает лямбда-выражение типа (Double) -> Double. Точное лямбда-выражение, возвращаемое функцией, зависит от значения переданной строки.
fun getConversionLambda(str: String): (Double) -> Double < if (str == "CentigradeToFahrenheit") < return < it * 1.8 + 32 >> else if (str == "KgsToPounds") < return < it * 2.204623 >> else if (str == "PoundsToUSTons") < return < it / 2000.0 >> else < return < it >> >
Вы можете вызвать лямбда-выражение, возвращённое функцией, или использовать его в аргументе при вызове другой функции. Например, следующий код выполняет возвращаемое значение getConversionLambda для пересчета 2,5 кг в фунты и присваивает его переменной с именем pounds:
val pounds = getConversionLambda("KgsToPounds")(2.5) println(pounds)
Также возможно определить функцию, которая получает и возвращает лямбда-выражение.
Условный оператор if-else в Kotlin
В языках программирования условные операторы – это инструмент для обеспечения ветвления в программах. Это значит, что в зависимости от сложившихся условий выполнение программы может идти по разным сценариям. Обычно не всей программы, а какой-то ее части. На блок-схемах ветвление обозначают ромбом, в котором может быть записано условие – логическое выражение, в процессе вычисления которого получается либо истина, либо ложь.
Рассмотрим простую программу на языке Kotlin с условным оператором и нарисуем соответствующую ей блок-схему. (Примечание. Здесь в логическом выражении используется двойное равенство. Это оператор сравнения, он возвращает истину, если левый операнд равен правому.)
fun main() { val s = readln() if (s == "Hello") println("World") else println("Epic fail") println("Bye") }

По блок-схеме программа выполняется сверху вниз. Сначала происходит связывание введенной пользователем строки с переменной s . Далее выполняется выражение, сравнивающее значение s со строкой «Hello». От результата этого сравнения будет зависеть, какая из двух следующих команд, находящихся на одном горизонтальном уровне, – println(«World») или println(«Epic fail») – будет выполнена. В случае true – левая, в случае false – правая.
Не может быть ситуации, когда будут выполнены обе команды. Либо одна, либо другая. Либо тело ветки if , либо тело ветки else .
Последняя команда, выводящая строку «Bye», будет исполнена в любом случае, так как она находится за пределами условного оператора if-else.
Тела веток условного оператора могут состоять из нескольких команд. В этом случае тело обязательно надо заключать в фигурные скобки.
import kotlin.random.Random fun main() { val ch1 = Random.nextInt(97,123) val ch2 = Random.nextInt(97,123) if (ch1 ch2) { println(ch1.toChar()) println(ch2.toChar()) println(ch2 - ch1) } else { println(ch2.toChar()) println(ch1.toChar()) println(ch1 - ch2) } }

В примере переменным ch1 и ch2 присваиваются случайные целые числа в диапазоне от 97 до 122 включительно. Эти номера соответствуют английским буквам в таблице символов. С помощью функции toChar() мы можем преобразовать числовой код в символ.
Ветвление позволяет нам всегда выводить на экран сначала букву с меньшим кодом, независимо от того, связана она с ch1 и ch2 . Также вычисляется расстояние между буквами. Операция сравнения
Условный оператор if-else может использоваться в урезанной форме, то есть без ветки else . Уберем ее в нашей первой программе:
fun main() { val s = readln() if (s == "Hello") println("World") println("Bye") }
Теперь, если пользователь введет что-то отличное от «Hello», то увидит на экране только «Bye». Если же введет «Hello», то ответом ему будет как «World», так и «Bye». Блок-схема примет такой вид:

В большинстве языков программирования if-else – это только оператор, команда языка. Так если слово val обозначает объявление переменной, то команда if . else просто создает в программе ветвление.
Однако в Kotlin if-else – это больше чем просто оператор, эта конструкция также является выражением. Особенностью выражений является то, что они что-нибудь возвращают в программу. Так выражение a + b вернет сумму чисел, выражение readln() – строку, даже println() возвращает «невидимый» объект (то, что она выводит на экран – это ее «работа», а не то, что она возвращает). Выражение if-else возвращает значение последнего выражения тела ветки, которая была исполнена.
Вспомним программу из предыдущего урока:
import kotlin.random.Random fun main() { val s: String = "number: " val i: Int = Random.nextInt(1,10) val f: Double if (i > 5) f = i * 1.5 else f = i * 2.0 println(s + i) println(f) }
Если конструкция if-else – это выражение, значит возвращаемое им значение можно присвоить переменной. Сделаем это, связав if-else с переменной v :
import kotlin.random.Random fun main() { val s: String = "number: " val i: Int = Random.nextInt(1,10) val f: Double val v = if (i > 5) f = i * 1.5 else f = i * 2.0 println(s + i) println(f) println(v) }
Пример выполнения программы:
number: 9 13.5 kotlin.Unit
Значение переменной v равно некому kotlin.Unit . Это специально предусмотренный в языке программирования объект-пустышка, его возвращают функции и другие выражения, когда возвращать им больше нечего. В нашем примере в теле ветки (неважно какой) после умножения выполняется операция присваивания. Эта операция и возвращает объект Unit , который связывается с переменной v .
Не путайте сам факт присваивания, когда значение связывается с f (это «работа» присваивания), и выражение присваивания, когда в программу что-то возвращается после его выполнения.
Если из тела убрать присваивание, останется только операция умножения. В этом случае переменной, связанной с выражением if-else, будет присваиваться произведение. Избавимся от переменной v и сразу присвоим f .
import kotlin.random.Random fun main() { val s: String = "number: " val i: Int = Random.nextInt(1,10) val f: Double f = if (i > 5) i * 1.5 else i * 2.0 println(s + i) println(f) }
Переменной f будет присвоено значение либо i * 1.5 , либо i * 2.0 . Код можно сократить, совместив присвоение с объявлением переменной:
import kotlin.random.Random fun main() { val s: String = "number: " val i: Int = Random.nextInt(1,10) val f = if (i > 5) i * 1.5 else i * 2.0 println(s + i) println(f) }
Несмотря на то, что Kotlin не только позволяет использовать if-else в качестве выражения, но и поощряет, в этом начальном курсе мы постараемся так не делать, так как это может усложнить восприятие и понимание кода, пока вы к нему не привыкните.
Практическая работа:
Напишите программу, которая генерирует случайное число от -5 до 5 включительно. Если это число оказывается отрицательным, то на экран выводится -1; если положительным – выводится 1, если равно нулю – выводится 0.
X Скрыть Наверх
Kotlin с нуля. Курс для начинающих
Основные типы
В Kotlin всё является объектом, в том смысле, что пользователь может вызвать функцию или получить доступ к свойству любой переменной. Некоторые типы являются встроенными, т.к. их реализация оптимизирована, хотя для пользователя они выглядят как обычные классы. В данном разделе описываются основные типы: числа, логические переменные, символы, строки и массивы.
Числа
Целочисленные типы
В Kotlin есть набор встроенных типов, которые представляют числа. Для целых чисел существует четыре типа с разными размерами и, следовательно, разными диапазонами значений.
| Тип | Размер (биты) | Минимальное значение | Максимальное значение |
|---|---|---|---|
| Byte | 8 | -128 | 127 |
| Short | 16 | -32768 | 32767 |
| Int | 32 | -2,147,483,648 (-2 31 ) | 2,147,483,647 (2 31 — 1) |
| Long | 64 | -9,223,372,036,854,775,808 (-2 63 ) | 9,223,372,036,854,775,807 (2 63 — 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, типы с плавающей точкой различаются своим десятичным разрядом, то есть количеством десятичных цифр, которые они могут хранить. С точки зрения IEEE 754 Float является одинарно точным, а Double обеспечивает двойную точность.
| Тип | Размер (биты) | Значимые биты | Биты экспоненты | Разряды |
|---|---|---|---|---|
| Float | 32 | 24 | 8 | 6-7 |
| Double | 64 | 53 | 11 | 15-16 |
Вы можете инициализировать переменные Double и Float числами, имеющими дробную часть. Она должна быть отделена от целой части точкой ( . ). Для переменных, инициализированных дробными числами, компилятор автоматически определяет тип Double .
val pi = 3.14 // Double // val one: Double = 1 // Ошибка: несоответствие типов val oneDouble = 1.0 // Double
Чтобы явно указать тип Float , добавьте после значения f или F . Если такое значение содержит более 6-7 разрядов, оно будет округлено.
val e = 2.7182818284 // Double val eFloat = 2.7182818284f // Float, фактическое значение 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) // Ошибка: несоответствие типов // printDouble(f) // Ошибка: несоответствие типов >
Чтобы преобразовать числовые значения в различные типы, используйте Явные преобразования.
Символьные постоянные
В языке Kotlin присутствуют следующие виды символьных постоянных (констант) для целых значений:
- Десятичные числа: 123
- Тип Long обозначается заглавной L : 123L
ВНИМАНИЕ: Восьмеричные литералы не поддерживаются.
Также Kotlin поддерживает числа с плавающей запятой:
- Тип Double по умолчанию: 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 и так далее.
Обратите внимание, что использование обёрток для одного и того же числа не гарантирует равенства ссылок на них.
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Все nullable-ссылки на a на самом деле являются одним и тем же объектом из-за оптимизации памяти, которую JVM применяет к Integer между «-128» и «127». Но b больше этих значений, поэтому ссылки на b являются разными объектами.
Однако, равенство по значению сохраняется.
val b: Int = 10000 println(b == b) // Prints 'true' val boxedB: Int? = b val anotherBoxedB: Int? = b println(boxedB == anotherBoxedB) // Prints 'true'Явные преобразования
Из-за разницы в представлениях меньшие типы не являются подтипами бОльших типов. В противном случае возникли бы сложности.
// Возможный код, который на самом деле не скомпилируется: val a: Int? = 1 // "Обёрнутый" Int (java.lang.Integer) val b: Long? = a // неявное преобразование возвращает "обёрнутый" Long (java.lang.Long) print(b == a) // Нежданчик! Данное выражение выведет "false" т. к. метод equals() типа Long предполагает, что вторая часть выражения также имеет тип LongТаким образом, будет утрачена не только тождественность (равенство по ссылке), но и равенство по значению.
Как следствие, неявное преобразование меньших типов в большие НЕ происходит. Это значит, что мы не можем присвоить значение типа Byte переменной типа Int без явного преобразования.
val b: Byte = 1 // всё хорошо, литералы проверяются статически // val i: Int = b // ОШИБКА val i1: Int = b.toInt()Каждый численный тип поддерживает следующие преобразования:
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
- toChar(): Char
Часто необходимости в явных преобразованиях нет, поскольку тип выводится из контекста, а арифметические действия перегружаются для подходящих преобразований.
val l = 1L + 3 // Long + Int => LongОперации
Котлин поддерживает стандартный набор арифметических операций над числами: + , — , * , / , % . Они объявляются членами соответствующих классов.
println(1 + 2) println(2_500_000_000L - 1L) println(3.14 * 2.71) println(10.0 / 3)Вы также можете переопределить эти операторы для пользовательских классов. См. Перегрузка операторов для деталей.
Деление целых чисел
Деление целых чисел всегда возвращает целое число. Любая дробная часть отбрасывается.
val x = 5 / 2 // println(x == 2.5) // ОШИБКА: Оператор '==' не может быть применен к 'Int' и 'Double' println(x == 2) // trueЭто справедливо для деления любых двух целочисленных типов.
val x = 5L / 2 println(x == 2L) // trueЧтобы вернуть тип с плавающей точкой, явно преобразуйте один из аргументов в тип с плавающей точкой.
val x = 5 / 2.toDouble() println(x == 2.5) // trueПобитовые операции
Kotlin поддерживает обычный набор побитовых операций над целыми числами. Они работают на двоичном уровне непосредственно с битовыми представлениями чисел. Побитовые операции представлены функциями, которые могут быть вызваны в инфиксной форме. Они могут быть применены только к Int и Long .
val x = (1 shl 2) and 0x000FF000Ниже приведён полный список битовых операций:
- shl(bits) – сдвиг влево с учётом знака (
- shr(bits) – сдвиг вправо с учётом знака ( >> в Java)
- ushr(bits) – сдвиг вправо без учёта знака ( >>> в Java)
- and(bits) – побитовое И
- or(bits) – побитовое ИЛИ
- xor(bits) – побитовое исключающее ИЛИ
- inv() – побитовое отрицание
Сравнение чисел с плавающей точкой
В этом разделе обсуждаются следующие операции над числами с плавающей запятой:
- Проверки на равенство: 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 для арифметики с плавающей точкой.
`, a type parameter), the operations use the `equals` and `compareTo` implementations for `Float` and `Double`, which disagree with the standard, so that: —>
Однако для поддержки общих вариантов использования и обеспечения полного упорядочивания, когда операнды статически не объявлены как числа с плавающей запятой (например, Any , Comparable <. >, параметр типа), операции используют реализации equals и compareTo для Float и Double , которые не согласуются со стандартом, так что:
- NaN считается равным самому себе
- NaN считается больше, чем любой другой элемент, включая «POSITIVE_INFINITY»
- -0.0 считается меньше, чем 0.0
Целые беззнаковые числа
В дополнение к целочисленным типам, в Kotlin есть следующие типы целых беззнаковых чисел:
- UByte : беззнаковое 8-битное целое число, в диапазоне от 0 до 255
- UShort : беззнаковое 16-битное целое число, в диапазоне от 0 до 65535
- UInt : беззнаковое 32-битное целое число, в диапазоне от 0 до 2 32 — 1
- ULong : беззнаковое 64-битное целое число, в диапазоне от 0 до 2 64 — 1
Беззнаковые типы поддерживают большинство операций своих знаковых аналогов.
Changing type from unsigned type to signed counterpart (and vice versa) is a *binary incompatible* change. —>
Изменение типа с беззнакового типа на его знаковый аналог (и наоборот) является двоично несовместимым изменением.
Беззнаковые массивы и диапазоны
Unsigned arrays and operations on them are in [Beta](components-stability.md). They can be changed incompatibly at any time. > Opt-in is required (see the details below). —>
Беззнаковые массивы и операции над ними находятся в стадии бета-тестирования. Они могут быть несовместимо изменены в любое время.
Как и в случае с примитивами, каждому типу без знака соответствует тип массивов знаковых типов:
- UByteArray : массив беззнаковых byte
- UShortArray : массив беззнаковых short
- UIntArray : массив беззнаковых int
- ULongArray : массив беззнаковых long
Как и целочисленные массивы со знаком, такие массивы предоставляют API, аналогичный классу Array , без дополнительных затрат на оборачивание.
При использовании массивов без знака вы получите предупреждение, что эта функция еще не стабильна. Чтобы удалить предупреждение используйте аннотацию @ExperimentalUnsignedTypes . Вам решать, должны ли ваши пользователи явно соглашаться на использование вашего API, но имейте в виду, что беззнаковый массив не является стабильной функцией, поэтому API, который он использует, может быть нарушен изменениями в языке. Узнайте больше о требованиях регистрации.
Диапазоны и прогрессии поддерживаются для UInt и ULong классами UIntRange , UIntProgression , ULongRange и ULongProgression . Вместе с целочисленными беззнаковыми типами эти классы стабильны.
Литералы
Чтобы целые беззнаковые числа было легче использовать, в Kotlin можно помечать целочисленный литерал суффиксом, указывающим на определенный беззнаковый тип (аналогично Float или Long ):
- u и U помечают беззнаковые литералы. Точный тип определяется на основе ожидаемого типа. Если ожидаемый тип не указан, компилятор будет использовать UInt или ULong в зависимости от размера литерала.
val b: UByte = 1u // UByte, есть ожидаемый тип val s: UShort = 1u // UShort, есть ожидаемый тип val l: ULong = 1u // ULong, есть ожидаемый тип val a1 = 42u // UInt: ожидаемого типа нет, константе подходит тип UInt val a2 = 0xFFFF_FFFF_FFFFu // ULong: ожидаемого типа нет, тип UInt не подходит константе- uL and UL явно помечают литерал как unsigned long .
val a = 1UL // ULong, даже несмотря на то, что ожидаемого типа нет и константа вписывается в UIntДальнейшее обсуждение
См. предложения для беззнаковых типов для технических деталей и дальнейшего обсуждения.
Логический тип
Тип Boolean представляет логический тип данных и принимает два значения: true и false .
При необходимости использования nullable-ссылок логические переменные оборачиваются Boolean? .
Встроенные действия над логическими переменными включают:
- || – ленивое логическое ИЛИ
- && – ленивое логическое И
- ! – отрицание
val myTrue: Boolean = true val myFalse: Boolean = false val boolNull: Boolean? = null println(myTrue || myFalse) println(myTrue && myFalse) println(!myTrue)**On JVM**: nullable references to boolean objects are boxed similarly to [numbers](#numbers-representation-on-the-jvm). —>
В JVM: nullable-ссылки на логические объекты заключены в рамки аналогично числам.
Символы
Символы в Kotlin представлены типом Char . Символьные литералы заключаются в одинарные кавычки: ‘1’ .
Специальные символы начинаются с обратного слеша \ . Поддерживаются следующие escape-последовательности: \t , \b , \n , \r , \’ , \» , \\ и \$ .
Для кодирования любого другого символа используйте синтаксис escape-последовательности Юникода: ‘\uFF00’ .
val aChar: Char = 'a' println(aChar) println('\n') // выводит дополнительный символ новой строки println('\uFF00')Если значение символьной переменной – цифра, её можно явно преобразовать в Int с помощью функции digitToInt() .
**On JVM**: Like [numbers](#numbers-representation-on-the-jvm), characters are boxed when a nullable reference is needed. >Identity is not preserved by the boxing operation. —>
В JVM: Подобно числам, символы оборачиваются при необходимости использования nullable-ссылки. При использовании обёрток тождественность (равенство по ссылке) не сохраняется.
Строки
Строки в Kotlin представлены типом String . Как правило, строка представляет собой последовательность символов в двойных кавычках ( » ).
val str = "abcd 123"Строки состоят из символов, которые могут быть получены по порядковому номеру: s[i] . Проход по строке выполняется циклом for .
for (c in str)
Строки являются неизменяемыми. После инициализации строки вы не можете изменить ее значение или присвоить ей новое. Все операции, преобразующие строки, возвращают новый объект String , оставляя исходную строку неизменной.
val str = "abcd" println(str.uppercase()) // Создается и выводится новый объект String println(str) // исходная строка остается прежнейДля объединения строк используется оператор + . Это работает и для объединения строк с другими типами, если первый элемент в выражении является строкой.
val s = "abc" + 1 println(s + "def") // abc1defОбратите внимание, что в большинстве случаев использование строковых шаблонов или обычных строк предпочтительнее объединения строк.
Строковые литералы
В 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(«>») .
Строковые шаблоны
Строки могут содержать шаблонные выражения, т.е. участки кода, которые выполняются, а полученный результат встраивается в строку. Шаблон начинается со знака доллара ( $ ) и состоит либо из простого имени (например, переменной),
val i = 10 println("i = $i") // выведет "i = 10"либо из произвольного выражения в фигурных скобках.
val s = "abc" println("$s.length is $") // выведет "abc.length is 3"Шаблоны поддерживаются как в обычных, так и в экранированных строках. При необходимости вставить символ $ в обычную строку (такие строки не поддерживают экранирование обратным слешом) перед любым символом, который разрешен в качестве начала идентификатора, используйте следующий синтаксис:
val price = """ $_9.99 """Массивы
Массивы в Kotlin представлены классом Array , обладающим функциями get и set (которые обозначаются [] согласно соглашению о перегрузке операторов), и свойством size , а также несколькими полезными встроенными функциями.
class Array private constructor() < val size: Int operator fun get(index: Int): T operator fun set(index: Int, value: T): Unit operator fun iterator(): Iterator// . >Для создания массива используйте функцию arrayOf() , которой в качестве аргумента передаются элементы массива, т.е. выполнение arrayOf(1, 2, 3) создаёт массив [1, 2, 3] . С другой стороны функция arrayOfNulls() может быть использована для создания массива заданного размера, заполненного значениями null .
Также для создания массива можно использовать фабричную функцию, которая принимает размер массива и функцию, возвращающую начальное значение каждого элемента по его индексу.
// создаёт массив типа Array со значениями ["0", "1", "4", "9", "16"] val asc = Array(5) < i ->(i * i).toString() > asc.forEach
Как отмечено выше, оператор [] используется вместо вызовов встроенных функций get() и set() .
` to an `Array `, which prevents a possible runtime failure (but you can use `Array `, see [Type Projections](generics.md#type-projections)). —>
Обратите внимание: в отличие от Java массивы в Kotlin являются инвариантными. Это значит, что Kotlin запрещает нам присваивать массив Array переменной типа Array , предотвращая таким образом возможный отказ во время исполнения (хотя вы можете использовать Array , см. Проекции типов).
Массивы примитивных типов
Также в Kotlin есть особые классы для представления массивов примитивных типов без дополнительных затрат на оборачивание: ByteArray , ShortArray , IntArray и т.д. Данные классы не наследуют класс Array , хотя и обладают тем же набором методов и свойств. У каждого из них есть соответствующая фабричная функция:
val x: IntArray = intArrayOf(1, 2, 3) x[0] = x[1] + x[2]// int массив, размером 5 со значениями [0, 0, 0, 0, 0] val arr = IntArray(5) // инициализация элементов массива константой // int массив, размером 5 со значениями [42, 42, 42, 42, 42] val arr = IntArray(5) < 42 >// инициализация элементов массива лямбда-выражением // int массив, размером 5 со значениями [0, 1, 2, 3, 4] (элементы инициализированы своим индексом) var arr = IntArray(5)
© 2015—2024 Open Source Community
Типы Any, Unit и Nothing в Kotlin
Тип Any можно назвать матерью всех прочих типов (кроме типов Null, которые мы рассмотрим в будущих уроках). В Kotlin каждый тип, будь то Int или String, считается Any . Это напоминает тип Object в Java, который является корнем все[ типов, кроме примитивных данных.
К примеру, в Kotlin можно объявить литералы Int и String как Any :
val anyNumber : Any = 42
val anyString : Any = «42»Тип Unit в Kotlin (void)
Unit является специальным типом, который всегда представляет только одно значение: объект Unit. Он похож на тип void в Java, только он упрощает работу с генериками, которые будут рассмотрены в будущих уроках. Каждая функция (думайте о функции как о фрагменте кода многократного использования), которая явно не возвращает тип, например String , возвращает Unit т.е. void если вам ассоциация с Java более близка.
К примеру, далее идет код функции, которая просто складывает 2 + 2 и как бы должна вывести результат, но на самом деле ничего не возвращает: