Kotlin


https://kotlinlang.org/docs/classes.html
https://kotlinlang.org/docs/inheritance.html
https://kotlinlang.org/docs/properties.html
https://kotlinlang.org/docs/interfaces.html
https://kotlinlang.org/docs/fun-interfaces.html#sam-conversions
https://kotlinlang.org/docs/visibility-modifiers.html
https://kotlinlang.org/docs/enum-classes.html
https://kotlinlang.org/docs/type-aliases.html
09.03.2024https://youtu.be/62XBpj2hrQw
20.01.2024https://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

TypeSizeMin valueMax value
Byte8 bits (1 byte)-128127
Short16 bits (2 byte)-32 76832 767
Int32 bits (4 byte)-2 147 483 648 (-2 в 31 степени)2 147 483 647 (2 в 31 степени - 1)
Long64 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

TypeSizeMin valueMax value
UByte8 bits (1 byte)0255
UShort16 bits (2 byte)065 535
UInt32 bits (4 byte)04 294 967 295 (2 в 32 - 1)
ULong64 bits (8 byte)018 446 744 073 709 551 615 (2 в 64 - 1)

Floating-point types

TypeSizeDecimal digits
Float32 bits (4 byte)6-7
Double64 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). Он позволяет передавать функции или свойства в качестве параметров другим функциям или сохранять их в переменные. Этот оператор может ссылаться как на функции, так и на свойства класса.

  1. Ссылка на функцию
fun greet() {
    println("Hello!")
}

val greetFunction = ::greet
greetFunction() // Вызовет функцию greet, результат: "Hello!"
  1. Ссылка на методы класса
class Greeter {
    fun sayHello() {
        println("Hello from the Greeter!")
    }
}

val greeter = Greeter()
val helloFunction = greeter::sayHello
helloFunction() // Выведет: "Hello from the Greeter!"
  1. Ссылка на конструктор
class Person(val name: String, val age: Int)

val createPerson = ::Person
val person = createPerson("John", 25)
println(person.name) // Выведет: "John"
  1. Ссылка на свойства
val x = 42
val propertyRef = ::x
println(propertyRef.get()) // Выведет: 42
  1. Использование в лямбдах
val numbers = listOf(1, 2, 3, 4, 5)
val squares = numbers.map(Int::toString)
println(squares) // Выведет: ["1", "2", "3", "4", "5"]
  1. 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:

  1. Контракт для проверки 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
}
  1. Контракт для условного выражения
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. Вопросы на собесе
  1. Чем Kotlin отличается от Java?
  1. В чем преимущество Kotlin в Andrid-разработке?
  1. Какие типы есть в Kotlin?
  1. Какие методы есть у класса Any?
  1. Что будет если вызвать lateinit var до объявления?
  1. Какие есть модификаторы доступа в Kotlin? В чем отличие от Java?
  1. Что такое null safety в Kotlin? Как осуществить проверку на null?
  1. Разница между === и ==?
  1. Расскажи про типы Any и Unit?
  1. В чем отличие исключений в Kotlin и Java?
  1. Для чего используется аннотация @JvmOverloads?

    Генерирует перегруженные версии функций или конструкторов с параметрами по умолчанию для улучшения совместимости с Java-кодом.

  1. Разница между val и var?

    В Kotlin val используется для создания неизменяемых переменных, значение которых можно установить только один раз, тогда как var создаёт изменяемые переменные, значение которых можно изменять.

  1. Разница между val и const val?

    val используется для создания неизменяемых переменных, которые могут быть инициализированы во время выполнения, а const val предназначен для констант, значение которых должно быть известно на этапе компиляции и не может изменяться.

  1. Всегда ли val возвращает одно и тоже значение?

    Не всегда. Если добавить get() - значение будет возвращаться каждый раз при обращении к свойству, и оно может изменяться в зависимости от логики геттера.

  1. Для чего нужен Nothing?

    Используется для обозначения типов, которые никогда не возвращают значения. Например, функции, которые всегда выбрасывают исключения или бесконечно выполняются. Nothing используется для улучшения типов и логики кода, указывая на неестественные или непредвиденные состояния.

  1. Почему в Kotlin нет примитивных типов?

    Потому что язык использует объекты для представления всех типов данных, включая те, которые в Java являются примитивами. Это упрощает работу с типами, делает язык более однородным, позволяет избежать проблем с боксингом/анбоксингом и различиями между примитивами и объектами, позволяет использовать типы в extension-функциях.

  1. Какие классы существуют в Kotlin?

    class, data class, value class, sealed class, inner class, nested class (просто класс внутри класса).

  1. На базе чего построен язык Kotlin?

    На базе Android Studio

    На базе языка Python

    На базе языка С++

    На базе платформы Intellij IDEA

    На базе Java Virtual Machine

  1. Какой синтаксис используется для создания функции, которая может принимать неограниченное количество аргументов?

    fun name(args: List<Any>)

    fun name(vararg args: Any)

    fun name(args: Any...)

    fun name(args: Array<Any>)

  1. Какого типа данных не существует в Kotlin?

    Array

    Object

    Int

    List

    Все перечисленные существуют

  1. Где неверно создана переменная?

    val str: String = null

    var num = 50;

    var number: Float = 45.001f

    var isGet: Boolean? = null

    var char = 'S'