Kotlin
09.03.2024 | https://youtu.be/62XBpj2hrQw |
20.01.2024 | https://youtu.be/9SHvJy5Sghg |
https://typealias.com/start/ |
Null safety
Cистема типов Kotlin различает ссылки на nullable и not null. Для безопасных вызовов используются операторы ?.
и ?:
(элвис). Для утверждающего вызова оператор !!
.
== vs ===
Оператор ==
сравнивает значения, а ===
ссылки на переменные.
val number = Integer(1)
val anotherNumber = Integer(1)
number == anotherNumber // true (structural equality)
number === anotherNumber // false (referential equality)
val
Постоянная переменная, может быть инициализирована 1 раз, вычисляется в run time. Аналог final в Java.
var
Мутабельная переменная.
const
Иммутабельна как и val, вычисляется в compile time
varargs
Передать переменное количество аргументов
init
Обычная inline-функция, вызывается после создания класса и вызова конструктора. Класс может содержать несколько блоков init, вызывающихся последовательно
lateinit
Используется с мутабельными значениями, когда поле не имеет изначального метода получения или установки значения (внедрение зависимостей, настройка тестов).
• если вызвать до объявления будет исключение UninitializedPropertyAccessException
: lateinit property has not been initialized.
constructor
В классах можно объявить один или несколько вторичных конструкторов. Каждый вторичный конструктор должен делегировать полномочия первичному конструктору через ключевое слово this
continue
Переходит к следующему шагу ближайшего вложенного цикла. Иногда при использовании цикла возникает необходимость при некоторых условиях не дожидаться выполнения всех инструкций в цикле, перейти к новой итерации. В данном случае когда n будет равно 5, сработает оператор continue. И последующая инструкция, которая выводит на консоль квадрат числа, не будет выполняться. Цикл перейдет к обработке следующего элемента в массиве
for (n in 1..8) {
if (n == 5) continue
println(n * n)
}
break
Завершает выполнение цикла. При некоторых условиях нам вовсе надо выйти из цикла, прекратить его выполнение. В данном случае когда n окажется равен 5, то с помощью оператора break будет выполнен выход из цикла. Цикл полностью завершится
for (n in 1..8) {
if (n == 5) break
println(n * n)
}
tailrec
Сообщить компилятору о выполнении хвостовой рекурсии. Хвостовая рекурсия - рекурсия, при которой любой рекурсивный вызов является последней операцией перед возвратом из функции.
tailrec fun fibonacci (n: Int, a: Long, b: Long): Long {
return if (n == 0) b else fibonacci (n - 1, a + b, a)
}
Kotlin Features, которых нет в Java
• Null-safety
• Именованные аргументы
• Выведение типов
• Extension-функции
• Локальные функции
• Операторы перегрузки
• Data-классы
• Sealed-классы
• DSL (Domain-Specific Languages)
Types
В Kotlin нет примитивных типов данных, все является объектами. Byte
Short
Int
Double
Char
Float
Long
Boolean
- все это объекты, наследуются от Any
и переопределяют его методы.
val a: Byte = -10
val b: Short = 45
val c: Int = -250
val d: Long = 30000L
val a: UByte = 10U
val b: UShort = 45U
val c: UInt = 250U
val d: ULong = 30000U
val a: Int = 0x0A1 // 161
val a: Int = 0b0101 // 5
val b: Int = 0b1011 // 11
val height: Double = 1.78
val pi: Float = 3.14F
val d: Double = 23e3 // 23 000
val g: Double = 23e-3 // 0.023
val a: Boolean = true
val b: Boolean = false
val a: Char = 'A'
val b: Char = 'B'
val c: Char = 'T'
val t: Char = '\t' // табуляция
val n: Char = '\n' // перевод строки
val r: Char = '\r' // возврат каретки
val a: Char = '\'' // одинарная кавычка
val b: Char = '\"' // двойная кавычка
val c: Char = '\\' // обратный слеш
val name: String = "Michael"
val name: String = """Michael"""
val range: IntRange = 1..5 // [1, 2, 3, 4, 5]
val range: CharRange = 'a'..'d'
val range: LongRange = 0L..1L
val range: ClosedRange<String> = "a".."d"
val range: IntProgression = 5 downTo 1 // 5 4 3 2 1
val range: IntProgression = 1..10 step 2 // 1 3 5 7 9
val range: IntProgression = 10 downTo 1 step 3 // 10 7 4 1
val range: IntProgression = 1 until 9 // 1 2 3 4 5 6 7 8
val range: IntProgression = 1 until 9 step 2 // 1 3 5 7
val isInRange: Boolean = 5 in 1..5
val isNotInRange: Boolean = 5 !in 1..5
val numbers: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val numbers = Array(3) { 5 } // [5, 5, 5]
var i = 1
val numbers = Array(3) { i++ * 2 } // [2, 4, 6]
val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5)
val doubles: DoubleArray = doubleArrayOf(2.4, 4.5, 1.2)
val numbers: IntArray = IntArray(3) { 5 }
val doubles: DoubleArray = DoubleArray(3) { 1.5 }
val table: Array<Array<Int>> = Array(3) { Array(5) { 0 } } // двумерный массив
fun changeNumbers(vararg values: Int, koef: Int) {}
val numbers: IntArray = intArrayOf(1, 2, 3, 4)
changeNumbers(*numbers, koef = 2)
Integer types
Type | Size | Min value | Max value |
---|---|---|---|
Byte | 8 bits (1 byte) | -128 | 127 |
Short | 16 bits (2 byte) | -32 768 | 32 767 |
Int | 32 bits (4 byte) | -2 147 483 648 (-2 в 31 степени) | 2 147 483 647 (2 в 31 степени - 1) |
Long | 64 bits (8 byte) | -9 223 372 036 854 775 808 (-2 в 63) | 9 223 372 036 854 775 807 (2 в 63 - 1) |
Unsigned Integer types
Type | Size | Min value | Max value |
---|---|---|---|
UByte | 8 bits (1 byte) | 0 | 255 |
UShort | 16 bits (2 byte) | 0 | 65 535 |
UInt | 32 bits (4 byte) | 0 | 4 294 967 295 (2 в 32 - 1) |
ULong | 64 bits (8 byte) | 0 | 18 446 744 073 709 551 615 (2 в 64 - 1) |
Floating-point types
Type | Size | Decimal digits |
---|---|---|
Float | 32 bits (4 byte) | 6-7 |
Double | 64 bits (8 byte) | 15-16 |
Boolean
Может хранить одно из двух значений: true (истина) или false (ложь)
Char
Отдельный символ, который заключается в одинарные кавычки.
CharSequence
Представляет читаемую последовательность значений Char
.
• в Kotlin представляет собой интерфейс.
• содержит поле length
.
• содержит методы get
и subSequence
.
String
Строка представляет последовательность символов, заключенную в двойные кавычки, либо в тройные двойные кавычки.
Number
Суперкласс для всех классов платформы, представляющих числовые значения.
Float vs Double?
Точность Double в 2 раза выше, чем Float. Double занимает 64 бит и может содержать 15 чисел после запятой, Float - 32 бит и 7 чисел после запятой
Целочисленные типы?
Объекты целочисленных типов хранят целые числа. Byte, Short, Int, Long. Начиная с Kotlin 1.5: UByte, UShort, UInt, ULong. Кроме чисел в десятичной системе мы можем определять числа в двоичной и шестнадцатеричной системах. Шестнадцатеричная запись числа начинается с 0x, затем идет набор символов от 0 до F, которые представляют число. Двоичная запись числа предваряется символами 0b, после которых идет последовательность из нулей и единиц
Числа с плавающей точкой?
Позволяют хранить дробные числа. В качестве разделителя целой и дробной части применяется точка. Double поддерживает экспоненциальную запись
Basic Types (Any, Unit, Nothing)
Any
Аналог Object
в Java, но с меньшим количеством методов: equals
hashCode
toString
. Все классы в Kotlin - наследники Any
.
Unit
Аналог void в Java. У функции возвращаемый тип можно не указывать, если она ничего не возвращает. Unit - объект, который наследуется от Any и имеет один метод - toString
Notning
Является подтипом всех типов Kotlin, даже классов с модификатором final
. Nothing
нельзя создать — у него приватный конструктор. Так как невозможно передать или вернуть тип Nothing
, он описывает результат «функции, которая никогда ничего не вернёт». Примером может быть функция, которая выбрасывает exception или в которой запущен бесконечный цикл: в любом из этих случаев она никогда не вернёт значения. В приложениях — независимо от того, какой тип данных возвращает функция, — она может никогда не вернуть данные, потому что произошла ошибка или вычисления затянулись на неопределённый срок. В этом случае имеет смысл использовать Nothing
. Который никогда не возвращает управление назад. Используют для методов Error, которые пробрасывают ошибку. Как прыгнуть с обрыва. Unit - прыгнуть с обрыва на веревке и вернуться назад без результата. Удобно использовать в generics, чтобы не возвращать nullable значения.
TODO("do this") // Пример функции Nothing
sealed class Result<out A: Any, out B: Any> {
data class Success<A: Any>(val value: A): Result<A, Nothing>()
data class Failure<B: Any>(val error: B): Result<Nothing, B>()
}
Math
Open-ended ranges ..<
Оператор для выражения диапазона значений. Работает как until
. Дает понять, что верхняя граница не включена
when (value) {
in 0.0..<0.25 -> // first quarter
in 0.25..<0.5 -> // second quarter
in 0.5..<0.75 -> // third quarter
in 0.75..1.0 -> // last quarter <- note closed range here
}
Операция по модулю
val a: Int = 65 % 10 // 5. Возвращает остаток от целочисленного деления двух чисел
// 65 / 10 = 6.5. Результатом будет число после запятой
// 10 % 3 = 1
// 10 / 20 = 0.5. Если целых до запятой нет то результатом будет числитель.
Операторы расширенного присвоения
val a: Int = 10
a += 2 // 12. Эквивалентно a = a + 2
a -= 2 // 8. Эквивалентно а = а - 2
a *= 2 // 20. Эквивалентно а = а * 2
a /= 2 // 5. Эквивалентно а = а / 2
a %= 3 // 1.
Операторы увеличения и уменьшения
var x: Int = 5
val y = ++x // Префиксный инкремент увеличивает значение на единицу. x должен быть var
printLn(x) // 6
printLn(y) // 6
var x = 5
val y = --x // Префиксный декремент уменьшает значение на единицу. x должен быть var
println(x) // x = 4
println(y) // y = 4
var x = 5
val y = x++ // Постфиксный инкремент возвращает значение до увеличения на единицу. x должен быть var
println(x) // x = 6
println(y) // y = 5
var x = 5
val y = x-- // Постфиксный декремент возвращает значение до уменьшения на единицу. x должен быть var
println(x) // x = 4
println(y) // y = 5
Round a number
val value: Double = 3.14123456789
val roundedValue: Double = value
.toBigDecimal()
.setScale(2, RoundingMode.HALF_EVEN)
.toDouble() // 3.14
::
operator
Оператор ссылки на функцию (function reference). Он позволяет передавать функции или свойства в качестве параметров другим функциям или сохранять их в переменные. Этот оператор может ссылаться как на функции, так и на свойства класса.
- Ссылка на функцию
fun greet() {
println("Hello!")
}
val greetFunction = ::greet
greetFunction() // Вызовет функцию greet, результат: "Hello!"
- Ссылка на методы класса
class Greeter {
fun sayHello() {
println("Hello from the Greeter!")
}
}
val greeter = Greeter()
val helloFunction = greeter::sayHello
helloFunction() // Выведет: "Hello from the Greeter!"
- Ссылка на конструктор
class Person(val name: String, val age: Int)
val createPerson = ::Person
val person = createPerson("John", 25)
println(person.name) // Выведет: "John"
- Ссылка на свойства
val x = 42
val propertyRef = ::x
println(propertyRef.get()) // Выведет: 42
- Использование в лямбдах
val numbers = listOf(1, 2, 3, 4, 5)
val squares = numbers.map(Int::toString)
println(squares) // Выведет: ["1", "2", "3", "4", "5"]
- C функциями расширениями:
fun String.printWithExclamation() {
println(this + "!")
}
val printExclaim = String::printWithExclamation
printExclaim("Hello") // Выведет: "Hello!"
Logical Bit Operations
and
Используется для побитового логического И между двумя значениями типа Int
, Long
, или других целочисленных типов. Оператор and выполняет побитовое сравнение двух чисел. Он сравнивает каждую пару битов двух чисел, и если оба бита равны 1, результатом будет 1, иначе — 0.
val a = 5 // в двоичной системе 0101
val b = 3 // в двоичной системе 0011
val result = a and b // результат 1, в двоичной системе 0001
println(result) // Выведет 1
or
Используется для выполнения побитовой операции “логическое ИЛИ” (bitwise OR) между целочисленными значениями (например, Int, Long). Оператор or выполняет побитовое сравнение двух чисел. Если хотя бы один бит из двух операндов равен 1, результатом для этого бита будет 1. Если оба бита равны 0, результатом будет 0.
val a = 5 // в двоичной системе: 0101
val b = 3 // в двоичной системе: 0011
val result = a or b // результат: 7 (в двоичной: 0111)
println(result) // Выведет 7
xor
Используется для выполнения побитовой операции “исключающее ИЛИ” между двумя целыми числами. Оператор обозначается символом ^.
// a xor b возвращает 1, если биты a и b различны (один из них 1, а другой 0).
// Возвращает 0, если биты a и b одинаковы (оба 0 или оба 1).
fun main() {
val a = 5 // Бинарное представление: 0101
val b = 3 // Бинарное представление: 0011
val result = a xor b // 0110 (6 в десятичной системе)
println(result) // Выведет: 6
}
Bit Shift Operations
Kotlin позволяет сдвигать биты целого числа влево или вправо (умножение или деления целого числа на степень двойки.).
shl
Оператор сдвига влево. Сдвигает биты целого числа влево на указанное количество позиций, сохраняя исходный знак целого числа. Младшие значащие биты с правой стороны значения заполняются нулями. Операция эквивалентна умножению целого числа на n^2.
@Test
fun `shifts bits left in an integer`() {
assertEquals(512, 128 shl 2)
}
shr
Оператор сдвига вправо. Сдвигает биты целого числа вправо на указанное количество позиций. Эта операция эквивалентна делению целого числа на n^2, сохраняя знак исходного целого числа.
@Test
fun `shifts bits right in a positive integer`() {
assertEquals(32, 128 shr 2)
}
@Test
fun `shifts bits right in a negative integer`() {
val a = -128 // 11111111111111111111111110000000
val expected = -32 // 11111111111111111111111111100000
assertEquals(expected, a shr 2)
}
ushr
Оператор сдвига вправо без знака. Сдвигает биты целого числа вправо на указанное количество позиций. Избыточные биты, сдвинутые вправо, отбрасываются, а нулевые биты всегда сдвигаются слева. В отличие от оператора shr
оператор ushr
не сохраняет знак исходного целого числа.
@Test
fun `shifts unsigned bits right in an integer`() {
val a = -128 // 11111111111111111111111110000000
val expected = 1073741792 // 00111111111111111111111111100000
assertEquals(expected, a ushr 2)
}
Bit Methods
inv
Используется для выполнения побитовой операции “NOT” (инверсия) над целым числом. Этот оператор инвертирует все биты числа, превращая 0 в 1 и 1 в 0. Для целых чисел, таких как Int или Long, операция inv меняет каждый бит числа на противоположный. Например, если в двоичном представлении число было 0101, то после применения inv получится 1010.
fun main() {
val a = 5 // Бинарное представление: 0000 0101
val result = a.inv() // Инвертирует все биты: 1111 1010
println(result) // Выведет: -6
}
countOneBits
Используется для подсчета количества единичных битов (1) в бинарном представлении числа.
fun main() {
val a = 5 // Бинарное представление: 0101
val count = a.countOneBits() // 2 (два единичных бита)
println(count) // Выведет: 2
}
countLeadingZeroBits
Используется для подсчета количества ведущих нулевых битов в бинарном представлении целого числа. Возвращает количество нулевых битов, расположенных в начале (слева) бинарного представления числа, до первого встреченного бита 1.
fun main() {
val a = 5 // Бинарное представление: 0000 0101
val count = a.countLeadingZeroBits() // 4
println(count) // Выведет: 4
}
countTrailingZeroBits
Используется для подсчета количества завершающих нулевых битов в бинарном представлении целого числа. Возвращает количество нулевых битов, расположенных в конце (справа) бинарного представления числа, после последнего встреченного бита 1.
fun main() {
val a = 40 // Бинарное представление: 0010 1000
val count = a.countTrailingZeroBits() // 3
println(count) // Выведет: 3
}
takeHighestOneBit
Возвращает значение, которое содержит только самый старший бит в бинарном представлении целого числа, установленных в 1. Все остальные биты устанавливаются в 0. Находит самый старший бит, установленный в 1, и устанавливает только этот бит в результирующее значение, в то время как все остальные биты устанавливаются в 0.
fun main() {
val a = 42 // Бинарное представление: 0010 1010
val result = a.takeHighestOneBit() // 0010 0000 (32 в десятичной системе)
println(result) // Выведет: 32
}
takeLowestOneBit
Возвращает значение, в котором установлен только самый младший бит, установленный в 1 в бинарном представлении числа. Все остальные биты устанавливаются в 0. Находит самый младший бит, установленный в 1, и устанавливает только этот бит в результирующее значение, в то время как все остальные биты устанавливаются в 0.
fun main() {
val a = 42 // Бинарное представление: 0010 1010
val result = a.takeLowestOneBit() // 0000 0010 (2 в десятичной системе)
println(result) // Выведет: 2
}
rotateLeft
Используется для побитового циклического сдвига (или вращения) чисел влево. Это означает, что биты числа сдвигаются влево на указанное количество позиций, а биты, вышедшие за пределы числа, переносятся в начало. Сдвигает биты числа влево на указанное количество позиций. Биты, которые “выпадают” за левую границу, возвращаются в начало числа с правой стороны.
fun main() {
val a = 9 // Бинарное представление: 0000 1001
val result = a.rotateLeft(2) // 0010 0100 (36 в десятичной системе)
println(result) // Выведет: 36
}
rotateRight
Используется для побитового циклического сдвига (или вращения) чисел вправо. Это означает, что биты числа сдвигаются вправо на указанное количество позиций, а биты, которые “выпадают” с правой стороны, переносятся в начало числа. Сдвигает биты числа вправо на указанное количество позиций. Биты, которые “выпадают” за правую границу, возвращаются в начало числа с левой стороны.
fun main() {
val a = 36 // Бинарное представление: 0010 0100
val result = a.rotateRight(2) // 0000 1001 (9 в десятичной системе)
println(result) // Выведет: 9
}
Properties
get
Получить значение свойства.
val number: Int
get() = 1
set
Присвоить новое значение свойству.
var number: Int = 0
set(value) {
field = value
}
var dependency: MyDependency? = null
@Inject set
Lambdas
Функция без названия (анонимная функция). Можно присвоить переменной и передавать как значение. Под капотом статический класс Java с 1 методом, который создается только один раз для всех вызовов
val printer: (String) -> Unit = { message: String ->
println(message)
}
printer("Hello")
val empty: (value: String) -> Boolean
get() = { it.isEmpty() }
empty("World)
Enums
Перечисления представляют тип данных, который позволяет определить набор логически связанных констант
enum class Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
enum class Day(val value: Int) {
MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);
fun getDuration(day: Day): Int {
return value - day.value;
}
}
val day: Day = Day.FRIDAY
println(day.name) // FRIDAY. Возвращает название константы в виде строки
println(day.ordinal) // 4. Возвращает порядковый номер константы
println(Day.values().map(Day::name)) // [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
println(Day.entries) // [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
Special Identifiers
it
Используется внутри лямбды для неявной ссылки на ее параметр.
field
Автоматически генерируемое поле, которое непосредственно хранит значение свойства.
var age: Int = 18
set(value) {
if (value < 100) {
field = value
}
}
Access Modifiers
public
Видны везде. Общедоступный модификатор по умолчанию.
private
Видны только в том файле, в котором определены
• несовместим с override и internal
• можно указать private open class Person
нельзя private open fun run()
protected
Видны в классах и наследниках
• нельзя использовать в методах верхнего уровня, только внутри классов
• если переопределить protected open метод и в наследнике добавить модификатор public - для метода ничего не изменится
open
Применяется, чтобы функции и свойства базового класса можно было переопределять. По умолчанию все классы final и без open класс не может быть унаследован
internal
Виден на уровне модуля. Не будут индексироваться в других модулях, не будут перегружать компилятор и автодополнение
• в Java нет модификатора internal, при компиляции станет просто общедоступным public
• в Java методы доступа к переменным помеченные как internal будут искажены, чтобы их сложнее было случайно вызвать. Класс при этом не обязательно помечать как internal
internal class Foo {
internal var name: String = "John"
}
Foo foo = new Foo();
foo.getName$android_app_template_app_main(); // get
foo.setName$android_app_template_app_main("name"); // set
final
Запретить повторное переопределение
open class Sam: Person() {
final override fun printName() {
println("Sam")
}
}
Exceptions
Исключение представляет событие, которое возникает при выполнении программы и нарушает ее нормальной ход. Конструкция try может возвращать значение. В отличие от Java, в Kotlin нет checked exceptions, исключения не объявляются явно в сигнатурах функций
try {
// код, генерирующий исключение
} catch (e: Exception) {
// обработка исключения
} finally {
// постобработка
}
throw
Генерирует исключение. Выражение throw имеет тип Nothing
throw Exception("Invalid value")
Throwable
Базовый класс для всех исключений.
@Throws
Сообщает вызывающим объектам Java, что функция может выдать исключение
@Throws(IllegalArgumentException::class)
fun addNumberToTwo(a: Any): Int {
if(a !is Int) {
throw IllegalArgumentException("Number must be an integer")
}
return 2 + a
}
Smart casts
is
Проверяет выражение на пренадлежность к определенному типу данных
hello is String
hello !is Int
as
Приводит значения одного типа к другому
hello as String
hello as? String
Operator overloading
operator
Определяет для типов ряд встроенных операторов
class Counter(var value: Int) {
operator fun plus(counter: Counter): Counter {
return Counter(this.value + counter.value)
}
operator fun minus(counter: Counter): Counter {
return Counter(this.value - counter.value)
}
operator fun compareTo(counter: Counter): Int {
return this.value - counter.value
}
}
operator fun Counter.plus(value: Int): Counter {
return Counter(this.value + value)
}
operator fun Int.plus(counter: Counter): Counter {
return Counter(this + counter.value)
}
val counter1 = Counter(5)
val counter2 = Counter(7)
val plus: Counter = counter1 + counter2
val plus: Counter = 4 + counter1
val plus: Counter = counter1 + 4
val minus: Counter = counter1 - counter2
val compareTo: Boolean = counter1 > counter2
Type-safe builders (DSL)
Типобезопасные статически-типизированные билдеры.
data class User(
var location: Location? = null
)
data class Location(
var long: String = ""
) {
fun long(lambda: () -> String) {
this.long = lambda()
}
}
fun user(lambda: User.() -> Unit): User {
val user = User()
user.apply(lambda)
return user
}
fun location(lambda: Location.() -> Unit): Location {
val location = Location()
location.apply(lambda)
return location
}
val user = user {
location = location {
long { "30.0" }
}
}
Functional Interfaces
Интерфейсы только с одним абстрактным методом называются функциональными интерфейсами или Single Abstract Method (SAM) интерфейсами. Функциональный интерфейс может иметь несколько неабстрактных членов, но только один абстрактный. Для функциональных интерфейсов вы можете использовать SAM преобразования, которые помогают сделать ваш код более лаконичным и читаемым, используя лямбда-выражения. Чтобы интерфейс был функциональным у него должна быть 1 функция без стандартной реализации. Другие функции могут быть с реализации.
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
// без преобразования
val isEven = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}
// с преобразованием
val isEven = IntPredicate { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}
Type Aliases
typealias
Псевдонимы типов. Предоставляют альтернативные имена для существующих типов. Если имя типа слишком длинное, можно создать другое более короткое и использовать вместо него
• nested and local type aliases are not supported
• к typealias можно применять модификаторы доступа public, private, internal
typealias OnClick = (view: View) -> Unit
class A {
inner class Inner
}
typealias AInner = A.Inner
typealias Predicate<T> = (T) -> Boolean
fun foo(p: Predicate<Int>) = p(42)
fun main() {
val f: (Int) -> Boolean = { it > 0 }
println(foo(f)) // prints "true"
val p: Predicate<Int> = { it > 0 }
println(listOf(1, -2).filter(p)) // prints "[1]"
}
Annotations
Эти аннотации помогают управлять тем, как Kotlin-код будет компилироваться и взаимодействовать с Java-кодом, обеспечивая гибкость и совместимость.
@JvmStatic
Делает методы или свойства компаньон-объекта или объекта синглтона доступными как статические методы или свойства в Java.
// В Java это будет доступно как MyClass.staticMethod().
class MyClass {
companion object {
@JvmStatic
fun staticMethod() {
// ...
}
}
}
@JvmOverloads
Генерирует перегруженные версии функции с параметрами по умолчанию для Java. Это позволяет вызывать функцию из Java без указания всех аргументов, которые имеют значения по умолчанию.
// В Java будут созданы перегруженные методы greet(String) и greet(String, int).
class MyClass {
@JvmOverloads
fun greet(message: String, times: Int = 1) {
// ...
}
}
@JvmField
Делает свойство публичным полем в Java, а не геттером/сеттером.
// В Java это будет доступно как myClass.someValue вместо myClass.getSomeValue().
class MyClass {
@JvmField
val someValue: Int = 42
}
@JvmInline
Используется для объявлений класса, который будет компилироваться как «вложенный» (inline) класс. Это позволяет уменьшить накладные расходы на создание экземпляров классов и улучшить производительность.
// В Java это будет представлено как обычный тип (например, int), что может улучшить производительность за счет уменьшения накладных расходов.
@JvmInline
value class MyValue(val value: Int)
@JvmName
Позволяет изменить имя класса, метода или свойства, видимое в Java. Это полезно для избежания конфликтов имен.
// В Java это будет доступно как renamedFunction().
@JvmName("renamedFunction")
fun myFunction() {
// ...
}
@JvmSynthetic
Прячет методы или свойства от Java-кода. Это позволяет скрыть их от Java, делая их доступными только в Kotlin.
class MyClass {
@JvmSynthetic
fun hiddenMethod() {
// ...
}
}
@JvmDefault
Позволяет задать реализацию по умолчанию для методов интерфейса в Java. Эта аннотация используется для обеспечения совместимости между Kotlin и Java, когда интерфейсы имеют методы по умолчанию.
interface MyInterface {
@JvmDefault
fun defaultMethod() {
// ...
}
}
Concurrent
@Synchronized
Аннотация для методов, геттеров и сеттеров. Метод будет защищен от одновременного выполнения несколькими потоками монитором экземпляра.
var value: Int = 0
@Synchronized get() = 5
@Synchronized set(value) { field = value }
@Synchronized
fun someMethod() {}
Preconditions
require
Функция для проверки аргументов.
Принимает параметры: value
и lazyMessage
.
Если value
= false, то функция вернет IllegalArgumentException
с сообщением lazyMessage
.
Можно передать только value
тогда текст ошибки будет стандартным: Failed requirement.
fun printPositiveNumber(num: Int) {
require(num > 0)
println(num)
}
fun printPositiveNumber(num: Int) {
require(num > 0) { "Number must be positive" }
println(num)
}
requireNotNull
Функция для проверки аргумента на null.
Принимает параметры: value
и lazyMessage
.
Если value = null, то функция вернет IllegalArgumentException
с сообщением lazyMessage
.
Можно передать только value
тогда текст ошибки будет стандартным: Required value was null.
private val _numberFlow = MutableStateFlow(0)
val numberFlow = _numberFlow.asStateFlow()
val number = requireNotNull(numberFlow.value)
check
Функция для проверки состояния.
Принимает параметры: value
и lazyMessage
.
Если value
= false, то функция вернет IllegalStateException
с сообщением переданным в lazyMessage
.
Можно передать только value
тогда текст ошибки будет стандартным: Check failed.
var someState: String = ""
val state = check(someState) { "State must be not empty" }
checkNotNull
Функция для проверки состояние на null.
Принимает параметры: value
и lazyMessage
.
Если value = null, то функция вернет IllegalStateException
с сообщением lazyMessage
.
Можно передать только value
тогда текст ошибки будет стандартным: Required value was null.
var someState: String? = null
val state = checkNotNull(someState) { "State must be not null" }
error
Функция для намеренного вызова исключения в коде. Выдаст IllegalStateException
.
fun divide(a: Int, b: Int): Int {
if (b == 0) {
error("Cannot divide by zero")
}
return a / b
}
Contracts
Позволяет уточнять контрактные соглашения о поведении функций, которые могут быть использованы для оптимизации компилятора и упрощения анализа кода. Контракты помогают компилятору Kotlin лучше понимать намерения разработчика и улучшать поведение кода за счет более точного анализа. Они могут быть полезны в ряде случаев, например, для корректной работы с условными выражениями, которые зависят от результата вызова функции, или для уточнения условий, которые должны выполняться для корректной работы кода.
Примеры использования Kotlin Contracts:
- Контракт для проверки null
fun requireNotNull(value: Any?): Any {
contract {
returnsNotNull() implies (value != null)
}
if (value == null) {
throw IllegalArgumentException("Value cannot be null")
}
return value
}
fun example() {
val x: String? = "Hello"
val y: String = requireNotNull(x) // После вызова, y не может быть null
}
- Контракт для условного выражения
fun <T> List<T>.isEmptyOrNull(): Boolean {
contract {
returns(true) implies (this@isEmptyOrNull == null || this@isEmptyOrNull.isEmpty())
}
return this == null || this.isEmpty()
}
fun example(list: List<String>?) {
if (list.isEmptyOrNull()) {
// list гарантированно пустой или null
}
}
Local Functions
Локальные функции — это функции, определенные внутри другой функции. Они могут быть вызваны только изнутри той функции, где они объявлены. Локальные функции полезны для структурирования кода и уменьшения дублирования, а также для повышения его читабельности.
fun main() {
fun outerFunction(number: Int): Int {
// Локальная функция
fun double(x: Int) = x * 2
// Использование локальной функции
return double(number) + double(number)
}
val result = outerFunction(5)
println(result) // Выведет: 20
}
Kotlin. Вопросы на собесе
- Чем Kotlin отличается от Java?
- В чем преимущество Kotlin в Andrid-разработке?
- Какие типы есть в Kotlin?
- Какие методы есть у класса Any?
- Что будет если вызвать lateinit var до объявления?
- Какие есть модификаторы доступа в Kotlin? В чем отличие от Java?
- Что такое null safety в Kotlin? Как осуществить проверку на null?
- Разница между === и ==?
- Расскажи про типы Any и Unit?
- В чем отличие исключений в Kotlin и Java?
- Для чего используется аннотация @JvmOverloads?
Генерирует перегруженные версии функций или конструкторов с параметрами по умолчанию для улучшения совместимости с Java-кодом.
- Разница между val и var?
В Kotlin
val
используется для создания неизменяемых переменных, значение которых можно установить только один раз, тогда какvar
создаёт изменяемые переменные, значение которых можно изменять.
- Разница между val и const val?
val
используется для создания неизменяемых переменных, которые могут быть инициализированы во время выполнения, аconst val
предназначен для констант, значение которых должно быть известно на этапе компиляции и не может изменяться.
- Всегда ли val возвращает одно и тоже значение?
Не всегда. Если добавить
get()
- значение будет возвращаться каждый раз при обращении к свойству, и оно может изменяться в зависимости от логики геттера.
- Для чего нужен Nothing?
Используется для обозначения типов, которые никогда не возвращают значения. Например, функции, которые всегда выбрасывают исключения или бесконечно выполняются. Nothing используется для улучшения типов и логики кода, указывая на неестественные или непредвиденные состояния.
- Почему в Kotlin нет примитивных типов?
Потому что язык использует объекты для представления всех типов данных, включая те, которые в Java являются примитивами. Это упрощает работу с типами, делает язык более однородным, позволяет избежать проблем с боксингом/анбоксингом и различиями между примитивами и объектами, позволяет использовать типы в extension-функциях.
- Какие классы существуют в Kotlin?
class
,data class
,value class
,sealed class
,inner class
, nested class (просто класс внутри класса).
- На базе чего построен язык Kotlin?
• На базе Android Studio
• На базе языка Python
• На базе языка С++
• На базе платформы Intellij IDEA
• На базе Java Virtual Machine
- Какой синтаксис используется для создания функции, которая может принимать неограниченное количество аргументов?
•
fun name(args: List<Any>)
•
fun name(vararg args: Any)
•
fun name(args: Any...)
•
fun name(args: Array<Any>)
- Какого типа данных не существует в Kotlin?
•
Array
•
Object
•
Int
•
List
• Все перечисленные существуют
- Где неверно создана переменная?
•
val str: String = null
•
var num = 50;
•
var number: Float = 45.001f
•
var isGet: Boolean? = null
•
var char = 'S'