Kotlin


https://kotlinlang.org/docs/home.html
https://kotlinlang.org/docs/basic-syntax.html
https://kotlinlang.org/docs/properties.html
09.03.2024https://youtu.be/62XBpj2hrQw
20.01.2024https://youtu.be/9SHvJy5Sghg
https://typealias.com/start/
Keywords

package import class interface fun object val var typealias constructor init companion override abstract final open enum sealed data inner tailrec operator infix external inline noinline crossinline const suspend lateinit if else when try catch finally for do while throw return break continue public private protected internal is in as out reified dynamic null true false annotation actual expect this super by get set

Null Safety
https://kotlinlang.org/docs/null-safety.html

Система предотвращения ошибок связанных с null. По умолчанию типы в Kotlin не могут быть null, что делает код безопаснее. Null safety в Kotlin минимизирует риск возникновения ошибок NullPointerException, делая код более надежным.

?.

Оператор безопасного вызова. Bспользуется для безопасного обращения к свойствам или методам nullable-объектов. Если объект равен null, дальнейшая цепочка вызовов будет проигнорирована.

val length = name?.length  // Возвращает длину строки или null
?:

Оператор Элвиса. Позволяет указать значение по умолчанию, если переменная равна null.

val length = name?.length ?: 0  // Если name == null, возвращается 0
!!

Оператор небезопасного приведения. Используется, когда уверены, что значение не null. Но если оно окажется null, выбросится исключение NullPointerException.

val length = name!!.length  // NullPointerException, если name == null
Packages and imports
https://kotlinlang.org/docs/packages.html
package

Используется для организации кода в модули и пространственные области, чтобы избежать конфликтов имен и упростить управление кодом. Пакеты помогают структурировать проект и указывают, где находится файл. Пакет указывается в начале файла и должен соответствовать структуре папок, в которой расположен файл.

// Файл расположен в папке src/main/kotlin/com/example/myapp

package com.example.myapp

class MyClass {
    fun greet() {
        println("Hello from MyClass!")
    }
}

fun main() {
    val myClass = MyClass()
    myClass.greet()
}
import

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

• Импорт класса

import java.util.Date

fun main() {
    val currentDate = Date()
    println(currentDate)
}

• Импорт функции

import kotlin.math.sqrt

fun main() {
    val number = 16.0
    val result = sqrt(number)
    println("Square root of $number is $result")
}

• Импорт с использованием псевдонимов

import kotlin.collections.HashMap as MapAlias

fun main() {
    val map = MapAlias<String, Int>()
    map["one"] = 1
    map["two"] = 2
    println(map)
}

• Импорт всех элементов пакета

import kotlin.math.*

fun main() {
    val number = 25.0
    println("Square root: ${sqrt(number)}")
    println("Cosine: ${cos(number)}")
}
typealias
https://kotlinlang.org/docs/type-aliases.html

Позволяет создать псевдоним для существующего типа. Это полезно, когда нужно упростить длинные или сложные типы, сделать код более читаемым или создать понятный псевдоним для конкретного контекста. typealias не создает новый тип, он просто указывает на уже существующий, и компилятор видит эти типы как идентичные. Поддерживают модификаторы доступа. Не могут быть вложенными и локальными.

• Упрощение сложного типа функции

typealias StringTransformer = (String) -> String

fun greet(transformer: StringTransformer) {
    println(transformer("Hello, World!"))
}

fun main() {
    val upperCaseTransformer: StringTransformer = { it.uppercase() }
    greet(upperCaseTransformer) // Выведет: HELLO, WORLD!
}

• Сокращение сложного типа коллекции

typealias UserMap = Map<String, List<Int>>

fun main() {
    val userScores: UserMap = mapOf(
        "Alice" to listOf(85, 90, 92),
        "Bob" to listOf(78, 80, 88)
    )
    println(userScores)
}

• Упрощение вложенных классов

class Outer {
    class Inner
}

typealias InnerClass = Outer.Inner

fun main() {
    val instance: InnerClass = InnerClass()
    println(instance)
}

• Улучшение читаемости в коде с использованием специальных типов

typealias Meter = Double
typealias Kilogram = Double

fun calculateBMI(height: Meter, weight: Kilogram): Double {
    return weight / (height * height)
}

fun main() {
    val height: Meter = 1.75
    val weight: Kilogram = 70.0
    println("BMI: ${calculateBMI(height, weight)}") // Выведет: BMI: 22.857142857142858
}

• Псевдоним для интерфейса

interface ClickListener {
    fun onClick()
}

typealias ButtonClickListener = ClickListener

class Button(private val listener: ButtonClickListener) {
    fun click() {
        listener.onClick()
    }
}

fun main() {
    val button = Button(object : ButtonClickListener {
        override fun onClick() {
            println("Button clicked!")
        }
    })
    button.click() // Выведет: Button clicked!
}
abstract

Используется для объявления абстрактных классов и абстрактных методов (или свойств). Абстрактный класс или метод представляет собой незавершенную реализацию, которую должны дополнять или переопределять классы-наследники.

• Абстрактный класс и метод

abstract class Animal {
    abstract fun makeSound()

    fun eat() {
        println("Eating food")
    }
}

class Dog: Animal() {
    override fun makeSound() {
        println("Bark")
    }
}

class Cat: Animal() {
    override fun makeSound() {
        println("Meow")
    }
}

fun main() {
    val dog = Dog()
    dog.makeSound() // Выведет: Bark
    dog.eat()       // Выведет: Eating food

    val cat = Cat()
    cat.makeSound() // Выведет: Meow
    cat.eat()       // Выведет: Eating food
}

• Абстрактные свойства

abstract class Vehicle {
    abstract val maxSpeed: Int
    abstract fun startEngine()

    fun showMaxSpeed() {
        println("Max speed is $maxSpeed km/h")
    }
}

class Car: Vehicle() {
    override val maxSpeed = 220

    override fun startEngine() {
        println("Car engine started")
    }
}

class Bike: Vehicle() {
    override val maxSpeed = 180

    override fun startEngine() {
        println("Bike engine started")
    }
}

fun main() {
    val car = Car()
    car.showMaxSpeed()  // Выведет: Max speed is 220 km/h
    car.startEngine()   // Выведет: Car engine started

    val bike = Bike()
    bike.showMaxSpeed() // Выведет: Max speed is 180 km/h
    bike.startEngine()  // Выведет: Bike engine started
}

• Использование абстрактного класса в наследовании

abstract class Employee(val name: String) {
    abstract fun calculateSalary(): Double

    fun showDetails() {
        println("Employee: $name, Salary: ${calculateSalary()}")
    }
}

class Developer(
    name: String, 
    private val hoursWorked: Int, 
    private val ratePerHour: Double
): Employee(name) {
    override fun calculateSalary(): Double {
        return hoursWorked * ratePerHour
    }
}

class Manager(
    name: String, 
    private val baseSalary: Double, 
    private val bonus: Double
): Employee(name) {
    override fun calculateSalary(): Double {
        return baseSalary + bonus
    }
}

fun main() {
    val dev = Developer("Alice", 160, 50.0)
    dev.showDetails() // Выведет: Employee: Alice, Salary: 8000.0

    val mgr = Manager("Bob", 5000.0, 2000.0)
    mgr.showDetails() // Выведет: Employee: Bob, Salary: 7000.0
}
Modifiers
https://kotlinlang.org/docs/visibility-modifiers.html
public

Указывает, что класс, функция, свойство или объект будут доступны везде в коде, без ограничений. В Kotlin public является модификатором по умолчанию, если явно не указан другой модификатор.

public class Person(val name: String) {
    public fun greet() {
        println("Hello, my name is $name")
    }
}

fun main() {
    val person = Person("Alice")
    person.greet() // Выведет: Hello, my name is Alice
}
protected

Ограничивает доступ к элементам (свойствам, методам или конструкторам) только в пределах класса и его подклассов. Применяется только к классам и интерфейсам.

open class Animal {
    protected fun makeSound() {
        println("Animal sound")
    }
}

class Dog: Animal() {
    fun bark() {
        makeSound() // Доступно, так как это подкласс
        println("Dog barks")
    }
}

fun main() {
    val dog = Dog()
    dog.bark() // Выведет: Animal sound, Dog barks
    // dog.makeSound() // Ошибка: makeSound() недоступен вне класса и его подклассов
}
private

Используется для ограничения доступа к классам, свойствам, методам и конструкторам. Он делает элемент доступным только в пределах того файла или класса, в котором он объявлен.

class Person(private val name: String) {
    private fun showName() {
        println("Name is $name")
    }

    fun introduce() {
        showName() // Можно вызвать внутри класса
    }
}

fun main() {
    val person = Person("Alice")
    person.introduce() // Выведет: Name is Alice
    // person.showName() // Ошибка: showName() недоступен вне класса
}
internal

Используется для ограничения доступа к элементам (классам, функциям, свойствам и конструкторам) в пределах одного модуля. Модуль — это набор файлов, которые компилируются вместе, например, проект, библиотека или единица сборки Gradle. В Java нет модификатора internal, при компиляции класс станет общедоступным public. Имена internal-методов будут искажены, чтобы их было сложнее случайно вызвать.

• На класс и его методы

internal class Car(val model: String) {
    internal fun drive() {
        println("$model is driving")
    }
}

fun main() {
    val car = Car("Toyota")
    car.drive() // Доступно, так как находится в том же модуле
}

• На уровне файла

internal fun calculateSum(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    println(calculateSum(5, 10)) // Доступно, так как находится в том же модуле
}

• Конструктор

class User internal constructor(val name: String) {
    fun greet() {
        println("Hello, $name")
    }
}

fun main() {
    val user = User("Alice") // Доступно, так как находится в том же модуле
    user.greet()
}
open

Используется для того, чтобы разрешить классу, методу или свойству быть унаследованными или переопределенными. В Kotlin по умолчанию все классы и методы являются закрытыми (final). Это сделано для повышения безопасности и предсказуемости кода.

open класс и наследование

open class Animal {
    open fun sound() {
        println("Some generic animal sound")
    }
}

class Dog: Animal() {
    override fun sound() {
        println("Bark!")
    }
}

fun main() {
    val dog = Dog()
    dog.sound() // Выведет: Bark!
}

open свойства

open class Car {
    open val maxSpeed: Int = 180

    open fun drive() {
        println("Driving at speed: $maxSpeed")
    }
}

class SportsCar: Car() {
    override val maxSpeed: Int = 300

    override fun drive() {
        println("Driving at high speed: $maxSpeed")
    }
}

fun main() {
    val car = SportsCar()
    car.drive() // Выведет: Driving at high speed: 300
}

open и protected

open class Vehicle {
    protected open fun startEngine() {
        println("Engine started")
    }
}

class Truck: Vehicle() {
    override fun startEngine() {
        println("Truck engine started")
    }

    fun start() {
        startEngine() // Доступен, так как он protected и переопределен
    }
}

fun main() {
    val truck = Truck()
    truck.start() // Выведет: Truck engine started
}
final

Используется для того, чтобы запретить повторное наследование или переопределение класса, метода или свойства.

open class GrandPa {
    open fun smoke() {
        println("Дед курит сигары")
    }
}
    
open class Dad: GrandPa() {
    final override fun smoke() {
        println("Батя унаследовал от деда привычку курить сигары, но запретил ее передачу сыну")
    }
}
    
class Son: Dad() {
    // Сын не может наследовать метод курить, потому что он final
}
Basic Types
Any

Базовый класс для всех типов (аналог Object в Java). Все классы Kotlin по умолчанию наследуют Any. Он включает 3 метода: equals(), hashCode(), и toString().

fun printInfo(obj: Any) {
    println("Class: ${obj::class.simpleName}")
}

fun main() {
    printInfo("Hello, Kotlin!")
    printInfo(123)
}
Unit

Тип, который указывает, что функция не возвращает значимого значения (аналогичен void в Java). Unit является полноценным типом и может использоваться как значение. Функция с типом Unit возвращает единственный объект Unit.

fun printMessage(): Unit {
    println("Hello, Kotlin!")
}
val printHello: () -> Unit = { println("Hello, World!") }

fun main() {
    printHello()
}
Notning

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

fun fail(): Nothing {
    throw IllegalArgumentException("Ошибка")
}

fun main() {
    fail() // Программа завершится с исключением
}
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>()
}
class
https://kotlinlang.org/docs/classes.html
https://kotlinlang.org/docs/inheritance.html

Ключево слово для определения класса в Kotlin. Классы содержат свойства и функции, а также поддерживают наследование и инкапсуляцию.

class Car(val make: String, var model: String) {
    fun display() {
        println("Car make: $make, model: $model")
    }
}

val car = Car("Toyota", "Camry")
car.display() // Вывод: Car make: Toyota, model: Camry
constructor

Конструкция constructor используется для объявления конструкторов классов. В языке есть два вида конструкторов: primary constructor (главный конструктор) и secondary constructors (вторичные конструкторы). Главный конструктор задается сразу при объявлении класса и является его основной точкой инициализации. Он может быть пустым или содержать параметры для передачи значений.

class Person(val name: String, var age: Int) {
    init {
        println("Создан объект с именем: $name и возрастом: $age")
    }
}

• Можно задать видимость конструктора с помощью модификаторов доступа private, protected и internal.

class Restricted private constructor(val name: String) {
    // В этом классе никто не сможет создать экземпляр через обычный конструктор
}

• Можно задавать значения по умолчанию для параметров конструктора.

class Employee(val name: String, val position: String = "Инженер")
Secondary Constructor

Вторичные конструкторы используются, когда нужно предоставить дополнительные варианты инициализации объекта. Они определяются внутри тела класса с использованием ключевого слова constructor.

• Может быть несколько вторичных конструкторов в одном классе.

• Вторичные конструкторы обязаны вызывать либо главный конструктор, либо другие вторичные конструкторы.

class Book {
    var title: String
    var author: String
    
    // Главный конструктор
    constructor(title: String) {
        this.title = title
        this.author = "Неизвестный автор"
    }

    // Вторичный конструктор, вызывает другой конструктор
    constructor(title: String, author: String): this(title) {
        this.author = author
    }
}
class Animal(val species: String) {
    var age: Int = 0

    // Вторичный конструктор
    constructor(species: String, age: Int): this(species) {
        this.age = age
    }
}
== vs ===

Оператор == сравнивает значения, а === ссылки на переменные.

val number = Integer(1)
val anotherNumber = Integer(1)
number == anotherNumber // true (structural equality)
number === anotherNumber // false (referential equality)
interface
https://kotlinlang.org/docs/interfaces.html
https://kotlinlang.org/docs/fun-interfaces.html

Используется для определения интерфейса. Интерфейс — это тип, который может содержать объявления абстрактных методов и свойств, а также предоставлять реализацию по умолчанию для методов.

• Базовый интерфейс с абстрактными методами

interface Animal {
    fun makeSound() // Абстрактный метод
}

class Dog: Animal {
    override fun makeSound() {
        println("Woof!")
    }
}

fun main() {
    val dog = Dog()
    dog.makeSound() // Выведет: Woof!
}

• Интерфейс с реализацией метода

interface Vehicle {
    fun startEngine() {
        println("Engine started")
    }

    fun stopEngine()
}

class Car: Vehicle {
    override fun stopEngine() {
        println("Engine stopped")
    }
}

fun main() {
    val car = Car()
    car.startEngine() // Выведет: Engine started
    car.stopEngine()  // Выведет: Engine stopped
}

• Интерфейс со свойствами

interface Shape {
    val name: String // Абстрактное свойство
    fun area(): Double
}

class Circle(private val radius: Double) : Shape {
    override val name: String
        get() = "Circle"

    override fun area(): Double = Math.PI * radius * radius
}

fun main() {
    val circle = Circle(5.0)
    println("Shape: ${circle.name}, Area: ${circle.area()}")
    // Выведет: Shape: Circle, Area: 78.53981633974483
}

• Множественная реализация интерфейсов

interface Drivable {
    fun drive()
}

interface Flyable {
    fun fly()
}

class FlyingCar: Drivable, Flyable {
    override fun drive() {
        println("Driving on the road")
    }

    override fun fly() {
        println("Flying in the sky")
    }
}

fun main() {
    val flyingCar = FlyingCar()
    flyingCar.drive() // Выведет: Driving on the road
    flyingCar.fly()   // Выведет: Flying in the sky
}

• Разрешение конфликта при множественной реализации

interface Clickable {
    fun click() = println("Clickable clicked")
}

interface Focusable {
    fun click() = println("Focusable clicked")
}

class Button: Clickable, Focusable {
    override fun click() {
        super<Clickable>.click() // Указываем, какую реализацию использовать
        super<Focusable>.click()
    }
}

fun main() {
    val button = Button()
    button.click()
    // Выведет:
    // Clickable clicked
    // Focusable clicked
}
fun interface

Cпециальный тип интерфейсов, предназначенный для функциональных интерфейсов (также известных как Single Abstract Method (SAM) интерфейсы). Это интерфейсы, которые содержат только один абстрактный метод, и они могут быть использованы для создания лямбд и сокращенного синтаксиса функциональных объектов.

fun interface Calculator {
    fun calculate(a: Int, b: Int): Int
}

fun main() {
    // Используем лямбду вместо создания объекта
    val sum: Calculator = Calculator { a, b -> a + b }
    println(sum.calculate(5, 3)) // Выведет: 8

    val multiply: Calculator = Calculator { a, b -> a * b }
    println(multiply.calculate(5, 3)) // Выведет: 15
}
init

Инициализационный блок, который выполняется каждый раз, когда создается объект класса. Он используется для выполнения дополнительных операций, которые нельзя поместить непосредственно в главный конструктор, таких как инициализация полей, валидация входных параметров или выполнение сторонних операций, которые требуют предварительного вызова.

• Запускается после того, как главный конструктор класса завершил инициализацию его параметров.

class Rectangle(val width: Int, val height: Int) {
    val area: Int

    init {
        // Инициализация свойства площади
        area = width * height
        println("Создан прямоугольник с шириной $width и высотой $height. Площадь: $area")
    }
}

• Может быть несколько блоков init: если в классе объявлено несколько блоков init, они выполняются в порядке их объявления сверху вниз.

class User(val name: String) {
    init {
        println("Первый блок инициализации: Привет, $name")
    }

    init {
        println("Второй блок инициализации: Добро пожаловать!")
    }
}

• Срабатывает перед вторичными конструкторами: даже если используются вторичные конструкторы, блоки init будут вызваны до того, как произойдет выполнение кода в этих конструкторах.

class Book(val title: String) {
    var author: String = "Неизвестный"

    init {
        println("Создана книга: $title")
    }

    constructor(title: String, author: String): this(title) {
        this.author = author
        println("Автор книги: $author")
    }
}

val book1 = Book("Книга без автора")
// Вывод:
// Создана книга: Книга без автора

val book2 = Book("Книга с автором", "Иван Иванов")
// Вывод:
// Создана книга: Книга с автором
// Автор книги: Иван Иванов

• В случае наследования блоки init также могут использоваться, но сначала будет выполнен блок init базового класса, а затем блоки наследника.

open class Animal(val species: String) {
    init {
        println("Создано животное вида $species")
    }
}

class Dog: Animal("Собака") {
    init {
        println("Создана собака")
    }
}

val dog = Dog()
// Вывод:
// Создано животное вида Собака
// Создана собака

• Блок init не запрещено использовать внутри object.

object MySingleton {
    val data: String

    init {
        data = "Initialized"
    }
    
    // Код инициализации можно написать напрямую
    val anotherData = "Direct Initialization"
}
enum
https://kotlinlang.org/docs/enum-classes.html

Cпециальный тип класса, который используется для представления набора фиксированных значений.

• Пустой enum-класс

enum class EmptyEnum

enum-класс с фиксированными значениями

enum class Direction {
    NORTH, EAST, SOUTH, WEST
}

enum-класс с целочисленными значениями

enum class Month(val number: Int) {
    JANUARY(1),
    FEBRUARY(2),
    MARCH(3),
    APRIL(4),
    MAY(5),
    JUNE(6),
    JULY(7),
    AUGUST(8),
    SEPTEMBER(9),
    OCTOBER(10),
    NOVEMBER(11),
    DECEMBER(12)
}

enum-класс со строковыми значениями

enum class Color(val hexCode: String) {
    RED("#FF0000"),
    GREEN("#00FF00"),
    BLUE("#0000FF")
}

enum-класс с несколькими параметрами

enum class Planet(val mass: Double, val radius: Double) {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS(4.869e+24, 6.0518e6),
    EARTH(5.976e+24, 6.37814e6),
    MARS(6.421e+23, 3.3972e6)
}

enum-класс с методом

enum class TrafficLight(val duration: Int) {
    RED(30),
    YELLOW(5),
    GREEN(25);

    fun printDuration() {
        println("The duration of $name is $duration seconds")
    }
}

fun main() {
    val light = TrafficLight.RED
    light.printDuration() // Выведет: The duration of RED is 30 seconds
}

enum-класс с переопределенными методами

enum class Operation {
    ADD {
        override fun apply(x: Int, y: Int) = x + y
    },
    SUBTRACT {
        override fun apply(x: Int, y: Int) = x - y
    },
    MULTIPLY {
        override fun apply(x: Int, y: Int) = x * y
    },
    DIVIDE {
        override fun apply(x: Int, y: Int) = x / y
    };

    abstract fun apply(x: Int, y: Int): Int
}

fun main() {
    val result = Operation.ADD.apply(5, 3)
    println(result) // Выведет: 8
}

enum-класс, реализующий интерфейс

interface Drawable {
    fun draw()
}

enum class Shape: Drawable {
    CIRCLE {
        override fun draw() {
            println("Drawing a circle")
        }
    },
    SQUARE {
        override fun draw() {
            println("Drawing a square")
        }
    },
    TRIANGLE {
        override fun draw() {
            println("Drawing a triangle")
        }
    }
}

fun main() {
    val shape = Shape.CIRCLE
    shape.draw() // Выведет: Drawing a circle
}

enum-класс с вложенными объектами (анонимные классы)

enum class Animal {
    DOG {
        override fun sound() = "Bark"
    },
    CAT {
        override fun sound() = "Meow"
    },
    COW {
        override fun sound() = "Moo"
    };

    abstract fun sound(): String
}

fun main() {
    val dogSound = Animal.DOG.sound()
    println(dogSound) // Выведет: Bark
}

enum-класс с лямбдами

enum class Operation(val perform: (Int, Int) -> Int) {
    ADD({ a, b -> a + b }),
    SUBTRACT({ a, b -> a - b }),
    MULTIPLY({ a, b -> a * b }),
    DIVIDE({ a, b -> a / b });

    fun apply(x: Int, y: Int): Int = perform(x, y)
}

fun main() {
    val result = Operation.ADD.apply(10, 5)
    println(result) // Выведет: 15

    val multiplication = Operation.MULTIPLY.apply(3, 4)
    println(multiplication) // Выведет: 12
}

enum-класс с объектами другого enum-класса

enum class Size {
    SMALL, MEDIUM, LARGE
}

enum class Drink(val size: Size) {
    COFFEE(Size.MEDIUM),
    TEA(Size.SMALL),
    SODA(Size.LARGE);
}

fun main() {
    val coffeeSize = Drink.COFFEE.size
    println("The size of coffee is: $coffeeSize") // Выведет: The size of coffee is: MEDIUM
}

enum-класс с другим классом

open class Animal(val sound: String) {
    fun makeSound() = println("This animal makes a $sound sound.")
}

enum class ZooAnimal(val animal: Animal) {
    LION(Animal("roar")),
    CAT(Animal("meow")),
    DOG(Animal("bark"));

    fun showSound() {
        animal.makeSound()
    }
}

fun main() {
    val lion = ZooAnimal.LION
    lion.showSound() // Выведет: This animal makes a roar sound.
}

enum-класс c data-классом

data class Car(val brand: String, val model: String, val year: Int)

enum class CarType(val car: Car) {
    SEDAN(Car("Toyota", "Camry", 2020)),
    SUV(Car("Ford", "Explorer", 2021)),
    TRUCK(Car("Chevrolet", "Silverado", 2019));

    fun printCarInfo() {
        println("Car Info: ${car.brand} ${car.model}, Year: ${car.year}")
    }
}

fun main() {
    val sedan = CarType.SEDAN
    sedan.printCarInfo() // Выведет: Car Info: Toyota Camry, Year: 2020
}

enum-класс c sealed-интерфейсом

sealed interface AnimalSound {
    fun makeSound(): String

    data object Bark : AnimalSound {
        override fun makeSound() = "Bark"
    }

    data object Meow : AnimalSound {
        override fun makeSound() = "Meow"
    }

    data object Moo : AnimalSound {
        override fun makeSound() = "Moo"
    }
}

enum class Animal(val sound: AnimalSound) {
    DOG(AnimalSound.Bark),
    CAT(AnimalSound.Meow),
    COW(AnimalSound.Moo);

    fun showSound() {
        println("The animal makes a sound: ${sound.makeSound()}")
    }
}

fun main() {
    val dog = Animal.DOG
    dog.showSound() // Выведет: The animal makes a sound: Bark

    val cat = Animal.CAT
    cat.showSound() // Выведет: The animal makes a sound: Meow

    val cow = Animal.COW
    cow.showSound() // Выведет: The animal makes a sound: Moo
}

enum-класс c sealed-классом

sealed class TransportMode(val speed: Int) {
    data object Car : TransportMode(120)
    data object Bicycle : TransportMode(20)
    data object Train : TransportMode(180)
}

enum class Transport(val mode: TransportMode) {
    CAR(TransportMode.Car),
    BICYCLE(TransportMode.Bicycle),
    TRAIN(TransportMode.Train);

    fun describe() {
        when (mode) {
            is TransportMode.Car -> println("Car can go up to ${mode.speed} km/h")
            is TransportMode.Bicycle -> println("Bicycle can go up to ${mode.speed} km/h")
            is TransportMode.Train -> println("Train can go up to ${mode.speed} km/h")
        }
    }
}

fun main() {
    val car = Transport.CAR
    car.describe() // Выведет: Car can go up to 120 km/h

    val bicycle = Transport.BICYCLE
    bicycle.describe() // Выведет: Bicycle can go up to 20 km/h
}
name

Возвращает имя элемента enum-класса в виде строки.

enum class Color {
    RED, GREEN, BLUE
}

fun main() {
    val color = Color.RED
    println(color.name) // Выведет: RED
}
ordinal

Возвращает порядковый номер (индекс) элемента в enum-классе.

enum class Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

fun main() {
    val day = Day.WEDNESDAY
    println("The ordinal of $day is ${day.ordinal}") // Выведет: The ordinal of WEDNESDAY is 2
}
entries

Возвращает список (List) всех элементов enum-класса.

enum class Direction {
    NORTH, EAST, SOUTH, WEST
}

fun main() {
    val directions: List<Direction> = Direction.entries
    directions.forEach { direction ->
        println(direction) // listOf(NORTH, EAST, SOUTH, WEST)
    }
}
values()

Возвращает массив (Array) всех элементов enum-класса. Метод устарел, рекомендуется заменить на Enum.entries начиная с версии Kotlin 1.9.

enum class Direction {
    NORTH, EAST, SOUTH, WEST
}

fun main() {
    val directions: Array<Direction> = Direction.values()
    directions.forEach { direction ->
        println(direction) // [NORTH, EAST, SOUTH, WEST]
    }
}
Variables
https://kotlinlang.org/docs/properties.html
val

Используется для объявления неизменяемых переменных, которые нельзя изменить после инициализации.

• Переменные, объявленные с помощью val, инициализируются значением, и это значение не может быть изменено в дальнейшем.

• Тип переменной определяется автоматически на основе присвоенного значения, но также может быть указан явно.

val требует немедленной инициализации при объявлении или инициализации в блоке инициализации.

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

val pi = 3.14
val name: String = "Alice"

class Example {
	  val a: Int
    
    init {
        a = 0
    }
}
var

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

• Переменные, объявленные с помощью var, могут быть инициализированы значением и изменены позже.

• Тип переменной определяется автоматически на основе присвоенного значения или может быть указан явно.

var может быть объявлен на уровне класса, функции или блока, определяя его область видимости.

var count = 0
count += 1 // Теперь count равно 1

var name: String = "Alice"
const

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

• Можно использовать только с переменными, которые имеют тип String или примитивные типы.

• Значения, объявленные с помощью const, должны быть известны на этапе компиляции и не могут быть изменены. Они не могут быть инициализированы с помощью выражений, которые зависят от выполнения кода.

• Делает код более читаемым и оптимизированным, так как компилятор заменяет все вхождения const переменной ее значением на этапе компиляции.

• Переменные должны быть объявлены на верхнем уровне файла или в именованном объекте или в companion object. Их нельзя объявить внутри функции или класса напрямую.

const val CONST_ON_TOP_LEVEL = "Name"

object MyObject {
		const val CONST_IN_NAMED_OBJECT = 10
}

class MyClass {
		companion object {
				const val CONST_IN_COMPANION_OBJECT = 'A'
		}
}
vararg

Механизм, позволяющий передавать переменное количество аргументов в функцию. Он упрощает написание функций, которые могут принимать не фиксированное количество параметров одного типа. Вместо того чтобы определять множество перегруженных методов, вы можете использовать vararg для обработки нескольких значений.

• В одной функции может быть только один параметр, объявленный как vararg.

• Параметр vararg должен быть последним в списке параметров функции.

• Можно комбинировать vararg с другими параметрами, но vararg всегда должен быть в конце.

fun printNumbers(vararg numbers: Int) {
    for (number in numbers) {
        println(number)
    }
}

printNumbers(1, 2, 3, 4, 5)  // Передача отдельных значений

val nums = intArrayOf(6, 7, 8)
printNumbers(*nums)  // Передача массива
lateinit

Модификатор, который используется для объявления переменных, которые будут инициализированы позже, но перед использованием. Он позволяет избежать необходимости присваивать значение переменной в момент её объявления, что особенно полезно для не-nullable переменных, которые не могут иметь значение по умолчанию.

lateinit можно использовать только с переменными, которые являются изменяемыми (var).

lateinit не могут иметь тип, основанный на примитивных типах (например, Int Long Float Double Boolean Char).

lateinit применим только к переменным класса, а не к локальным переменным в методах.

• Если попытаться получить доступ к lateinit-переменной до инициализации, будет выброшено исключение UninitializedPropertyAccessException.

class Example {
    lateinit var name: String
}

fun main() {
    val example = Example()
    example.name = "Kotlin"  // Инициализация
    println(example.name)    // Вывод: Kotlin
}

fun main() {
    val example = Example()
    if (::example.name.isInitialized) {
        println(example.name)
    } else {
        println("name не инициализирована")
    }
}
Functions
https://kotlinlang.org/docs/functions.html
fun

Используется для объявления функций.

fun sum(a: Int, b: Int): Int {
    return a + b
}

• Функции поддерживают значения по умолчанию.

fun greet(name: String = "Гость") {
    println("Привет, $name!")
}

// Вызывается как greet() или greet("Иван")

• Функции поддерживают переменное количество аргументов.

fun printNumbers(vararg numbers: Int) {
    for (number in numbers) {
        println(number)
    }
}

// Пример вызова: printNumbers(1, 2, 3, 4)
tailrec

Используется для оптимизации хвостовой рекурсии. Если функция является хвосторекурсивной (рекурсивный вызов является последней операцией в функции), компилятор может преобразовать её в цикл, что предотвращает переполнение стека вызовов. Это позволяет использовать рекурсию в ситуациях, где обычно использовался бы цикл, сохраняя читаемость кода.

// Здесь функция factorial вычисляет факториал числа.
// Благодаря ключевому слову tailrec, компилятор оптимизирует рекурсивные вызовы в цикл, что предотвращает переполнение стека при больших значениях n.

tailrec fun factorial(n: Int, acc: Int = 1): Int {
    return if (n <= 1) acc else factorial(n - 1, acc * n)
}

fun main() {
    println(factorial(5))  // Вывод: 120
}
operator
https://kotlinlang.org/docs/operator-overloading.html

Используется для перегрузки операторов. Оно позволяет разработчикам определять, как стандартные операторы (например, +, -, *, ==, [], и т. д.) должны вести себя при применении к объектам определенного класса. Это делает код более читаемым и интуитивно понятным, позволяя использовать синтаксис, похожий на встроенные типы данных, для своих собственных классов.

• Перегрузка оператора сложения +

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

fun main() {
    val p1 = Point(3, 4)
    val p2 = Point(1, 2)
    val p3 = p1 + p2 // Используется перегруженный оператор +
    println(p3) // Выведет: Point(x=4, y=6)
}

• Перегрузка оператора вычитания -

data class Point(val x: Int, val y: Int) {
    operator fun minus(other: Point): Point {
        return Point(x - other.x, y - other.y)
    }
}

fun main() {
    val p1 = Point(5, 7)
    val p2 = Point(2, 3)
    println(p1 - p2) // Выведет: Point(x=3, y=4)
}

• Перегрузка оператора умножения *

data class Vector(val x: Int, val y: Int) {
    operator fun times(scale: Int): Vector {
        return Vector(x * scale, y * scale)
    }
}

fun main() {
    val v = Vector(2, 3)
    val scaled = v * 5 // Используется перегруженный оператор *
    println(scaled) // Выведет: Vector(x=10, y=15)
}

• Перегрузка оператора деления /

data class Fraction(val numerator: Int, val denominator: Int) {
    operator fun div(other: Fraction): Fraction {
        return Fraction(
            numerator * other.denominator,
            denominator * other.numerator
        )
    }
}

fun main() {
    val f1 = Fraction(3, 4)
    val f2 = Fraction(2, 5)
    println(f1 / f2) // Выведет: Fraction(numerator=15, denominator=8)
}

• Перегрузка оператора остатка от деления %

data class ModNumber(val value: Int) {
    operator fun rem(other: Int): Int {
        return value % other
    }
}

fun main() {
    val num = ModNumber(10)
    println(num % 3) // Выведет: 1
}

• Перегрузка операторов равенства ==

data class Person(val name: String, val age: Int) {
    operator fun equals(other: Person): Boolean {
        return this.name == other.name && this.age == other.age
    }
}

fun main() {
    val person1 = Person("Alice", 25)
    val person2 = Person("Alice", 25)
    println(person1 == person2) // Выведет: true
    println(person1 != person2) // Выведет: false
}

• Перегрузка операторов сравнения > < >= <=

data class Box(val weight: Int) : Comparable<Box> {
    override operator fun compareTo(other: Box): Int {
        return this.weight - other.weight
    }
}

fun main() {
    val box1 = Box(5)
    val box2 = Box(10)
    println(box1 < box2)  // Выведет: true
    println(box1 > box2)  // Выведет: false
    println(box1 <= box2) // Выведет: true
    println(box1 >= box2) // Выведет: false
}

• Перегрузка операторов инкремента и декремента ++ --

data class Counter(var value: Int) {
    operator fun inc(): Counter {
        value++
        return this
    }
    
    operator fun dec(): Counter {
        value--
        return this
    }
}

fun main() {
    var counter = Counter(5)
    counter++
    println(counter.value) // Выведет: 6
    counter--
    println(counter.value) // Выведет: 5
}

• Перегрузка оператора логического отрицания !

data class Light(val isOn: Boolean) {
    operator fun not() = Light(!isOn)
}

fun main() {
    val light = Light(true)
    println(!light) // Выведет: Light(isOn=false)
}

• Перегрузка оператора доступа к элементу get

class Matrix(private val data: Array<Array<Int>>) {
    operator fun get(row: Int, col: Int): Int {
        return data[row][col]
    }
}

fun main() {
    val matrix = Matrix(arrayOf(arrayOf(1, 2), arrayOf(3, 4)))
    println(matrix[0, 1]) // Используется перегруженный оператор []
    // Выведет: 2
}

• Перегрузка оператора установки значения set

class MutableListWrapper<T>(private val list: MutableList<T>) {
    operator fun set(index: Int, value: T) {
        list[index] = value
    }
    
    override fun toString(): String {
        return list.toString()
    }
}

fun main() {
    val list = MutableListWrapper(mutableListOf(1, 2, 3))
    list[1] = 10
    println(list) // Выведет: [1, 10, 3]
}

• Перегрузка оператора вызова функции ()

class Greeter(val greeting: String) {
    operator fun invoke(name: String) {
        println("$greeting, $name!")
    }
}

fun main() {
    val greeter = Greeter("Hello")
    greeter("Kotlin") // Выведет: Hello, Kotlin!
}
it

Имя параметра по умолчанию для одиночного параметра в лямбда-выражении. Когда лямбда принимает только один аргумент, его можно не называть явно, и тогда к нему можно обращаться через it.

val numbers = listOf(1, 2, 3, 4, 5)
val doubledNumbers = numbers.map { it * 2 }

println(doubledNumbers) // Выведет: [2, 4, 6, 8, 10]
::

Оператор ссылки на функцию (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!"
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
}
High Order Functions
https://kotlinlang.org/docs/lambdas.html

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

// Эта функция принимает значение value и функцию func, которая преобразует value.
// Она применяет функцию дважды и возвращает результат.

fun <T> applyTwice(value: T, func: (T) -> T): T {
    return func(func(value))
}
// Функция createMultiplier возвращает функцию, которая умножает входное число на заданный коэффициент.

fun createMultiplier(factor: Int): (Int) -> Int {
    return { number -> number * factor }
}
Anonymous Functions

Анонимная функция — это функция без имени, которую можно объявить и использовать в месте, где она нужна. В Kotlin они могут быть созданы с помощью ключевого слова fun. Обычно используются для кратковременных операций и передаются как аргументы в другие функции. Это похоже на лямбда-выражения, но с явным определением типов и тела функции.

val sum = fun(a: Int, b: Int): Int {
    return a + b
}
super

Используется для вызова методов или свойств родительского класса или интерфейса из класса-потомка.

Вызов метода суперкласса

open class Animal {
    open fun makeSound() {
        println("Animal makes a sound")
    }
}

class Dog: Animal() {
    override fun makeSound() {
        super.makeSound() // Вызов метода суперкласса Animal
        println("Dog barks")
    }
}

fun main() {
    val dog = Dog()
    dog.makeSound()
    // Выведет:
    // Animal makes a sound
    // Dog barks
}

Доступ к свойству суперкласса

open class Person {
    open val name: String = "Unknown"
}

class Employee: Person() {
    override val name: String = "John Doe"
    
    fun printNames() {
        println("Employee name: $name")
        println("Person name: ${super.name}")
    }
}

fun main() {
    val employee = Employee()
    employee.printNames()
    // Выведет:
    // Employee name: John Doe
    // Person name: Unknown
}

Вызов метода из конкретного интерфейса

interface A {
    fun show() {
        println("Interface A")
    }
}

interface B {
    fun show() {
        println("Interface B")
    }
}

class C: A, B {
    override fun show() {
        super<A>.show() // Вызов метода из интерфейса A
        super<B>.show() // Вызов метода из интерфейса B
        println("Class C")
    }
}

fun main() {
    val c = C()
    c.show()
    // Выведет:
    // Interface A
    // Interface B
    // Class C
}
if - else

Используется как оператор для выполнения условного кода и как выражение, которое может возвращать значение.

if в Kotlin всегда должен иметь ветку else, если используется как выражение.

• Он может быть многострочным, если нужно выполнить несколько инструкций в каждой ветке.

// Пример как оператора:
val x = 10
if (x > 5) {
    println("x больше 5")
} else {
    println("x меньше или равно 5")
}
// Пример как выражения:
val max = if (a > b) a else b
Loops
while

Выполняет блок кода, пока условие истинно.

var i = 0
while (i < 5) {
    println(i)
    i++
}
do - while

Cначала выполняет блок кода, а затем проверяет условие. Он гарантирует хотя бы одно выполнение.

var i = 0
do {
    println(i)
    i++
} while (i < 5)
for

Используется для итерации по коллекциям, диапазонам и последовательностям.

Итерация по диапазону. Используется оператор in. Итерация проходит от начального до конечного значения (включительно):

for (i in 1..5) {
    println(i)  // Выведет: 1, 2, 3, 4, 5
}

until — итерация до верхней границы (исключительно):

for (i in 1 until 5) {
    println(i)  // Выведет: 1, 2, 3, 4 (5 не включено)
}

downTo — итерация в обратном порядке:

for (i in 5 downTo 1) {
    println(i)  // Выведет: 5, 4, 3, 2, 1
}

step — позволяет задать шаг для увеличения или уменьшения значений:

for (i in 1..10 step 2) {
    println(i)  // Выведет: 1, 3, 5, 7, 9
}

for (i in 10 downTo 1 step 3) {
    println(i)  // Выведет: 10, 7, 4, 1
}

Итерация по коллекции. Итерация по элементам коллекции (например, список или массив):

val items = listOf("a", "b", "c")
for (item in items) {
    println(item)  // Выведет: a, b, c
}

Итерация по индексам коллекции. Можно итерироваться по индексам коллекции с использованием .indices:

val items = listOf("a", "b", "c")
for (i in items.indices) {
    println("Index $i contains ${items[i]}")
}

Итерация по паре индекс-значение. Используя функцию .withIndex(), можно получать одновременно и индекс, и элемент:

val items = listOf("a", "b", "c")
for ((index, value) in items.withIndex()) {
    println("Item at index $index is $value")
}
break

Досрочно завершает выполнение цикла. После его вызова цикл немедленно прерывается.

val items = listOf("a", "b", "c", "d")
for (item in items) {
    if (item == "c") break  // Прерываем цикл, когда находим "c"
    println(item)  // выведет: a, b
}
continue

Пропускает оставшуюся часть текущей итерации и переходит к следующей.

val items = listOf("a", "b", "c", "d")
for (item in items) {
    if (item == "c") continue  // Пропускаем "c" и продолжаем с "d"
    println(item)  // выведет: a, b, d
}
Labels

Позволяют управлять потоком выполнения кода, особенно когда нужно прервать цикл, вернуть значение или продолжить выполнение из вложенного блока. Создаются с помощью символа @, и их можно использовать вместе с return, break, и continue для указания конкретного уровня блока или лямбды, к которому нужно вернуться.

break@label

Позволяет прервать вложенный цикл, чтобы выйти сразу из нескольких уровней. break@outerLoop прерывает выполнение не только внутреннего, но и внешнего цикла, помеченного outerLoop@.

fun findNumber(matrix: List<List<Int>>, target: Int) {
    outerLoop@ for (row in matrix) {       // Лейбл для внешнего цикла
        for (cell in row) {
            if (cell == target) {
                println("Found $target")
                break@outerLoop            // Выход из внешнего цикла
            }
        }
    }
    println("Search complete")
}
continue@label

Пропускает текущую итерацию определённого цикла, полезно при работе с вложенными циклами. Если cell == 0, continue@outerLoop пропустит текущую итерацию внешнего цикла outerLoop, переходя сразу к следующей строке matrix.

fun processMatrix(matrix: List<List<Int>>) {
    outerLoop@ for (row in matrix) {          // Лейбл для внешнего цикла
        for (cell in row) {
            if (cell == 0) continue@outerLoop // Переход к следующей итерации внешнего цикла
            println(cell)
        }
    }
}
return@label

return в лямбде по умолчанию используется для завершения выполнения всей функции, содержащей эту лямбду. Чтобы завершить только саму лямбду, используют лейбл. Например, return@forEach завершает только текущую итерацию лямбды forEach, не прерывая выполнение всей функции processNumbers.

fun processNumbers(numbers: List<Int>) {
    numbers.forEach {
        if (it == 0) return@forEach        // Завершает только текущую итерацию в лямбде
        println(it)
    }
    println("Completed processing")
}
Types
https://kotlinlang.org/docs/arrays.html

В 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
https://kotlinlang.org/docs/numbers.html
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
https://kotlinlang.org/docs/unsigned-integer-types.html
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
https://kotlinlang.org/docs/booleans.html

Тип данных для представления логических значений, который может содержать одно из двух значений: true или false. Boolean используется в условных операторах, циклах и для хранения результатов логических выражений.

val isAdult: Boolean = true
Char
https://kotlinlang.org/docs/characters.html

Тип данных, представляющий символ. Символ — это отдельный элемент текста, который может быть буквой, цифрой, специальным символом или любым другим символом из набора Unicode. В отличие от строк, Char представляет один символ и обозначается в коде одиночными кавычками.

val letter: Char = 'A'
val smiley: Char = '\u263A' // Unicode для ☺
code

Возвращает числовое значение (код) символа.

val char: Char = 'A'
val charCode: Int = char.code // Преобразование Char в Int
println("Code of '$char': $charCode") // Выведет: Code of 'A': 65
toChar

Преобразует число обратно в символ.

val newChar: Char = charCode.toChar() // Преобразование Int в Char
println("Char from code $charCode: $newChar") // Выведет: Char from code 65: A
isDigit

Проверяет, является ли символ цифрой (0-9).

fun main() {
    val char1: Char = '5'
    val char2: Char = 'a'
    
    println("Is '$char1' a digit? ${char1.isDigit()}") // Выведет: Is '5' a digit? true
    println("Is '$char2' a digit? ${char2.isDigit()}") // Выведет: Is 'a' a digit? false
}
isLetter

Проверяет, является ли символ буквой (любая буква алфавита, включая латиницу и другие символы Unicode).

fun main() {
    val char1: Char = 'K'
    val char2: Char = '3'
    
    println("Is '$char1' a letter? ${char1.isLetter()}") // Выведет: Is 'K' a letter? true
    println("Is '$char2' a letter? ${char2.isLetter()}") // Выведет: Is '3' a letter? false
}
isWhitespace

Проверяет, является ли символ пробелом или другим разделителем (например, пробел, табуляция, перевод строки и другие символы-разделители).

fun main() {
    val char1: Char = ' '
    val char2: Char = '\n'
    val char3: Char = 'a'
    
    println("Is ' ' a whitespace? ${char1.isWhitespace()}") // Выведет: Is ' ' a whitespace? true
    println("Is '\\n' a whitespace? ${char2.isWhitespace()}") // Выведет: Is '\n' a whitespace? true
    println("Is 'a' a whitespace? ${char3.isWhitespace()}") // Выведет: Is 'a' a whitespace? false
}
isUpperCase

Проверяет, является ли символ заглавной буквой.

fun main() {
    val char1: Char = 'G'
    val char2: Char = 'g'
    
    println("Is '$char1' uppercase? ${char1.isUpperCase()}") // Выведет: Is 'G' uppercase? true
    println("Is '$char2' uppercase? ${char2.isUpperCase()}") // Выведет: Is 'g' uppercase? false
}
isLowerCase

Проверяет, является ли символ строчной буквой.

fun main() {
    val char1: Char = 'm'
    val char2: Char = 'M'
    
    println("Is '$char1' lowercase? ${char1.isLowerCase()}") // Выведет: Is 'm' lowercase? true
    println("Is '$char2' lowercase? ${char2.isLowerCase()}") // Выведет: Is 'M' lowercase? false
}
Таблица Unicode
СимволUnicodeОписаниеПример на Kotlin
' ''\u0020'Пробел (Space)val char = '\\u0020'
'\\n''\u000A'Перенос строки (Line Feed)val char = '\\u000A'
'\\r''\u000D'Возврат каретки (Carriage Return)val char = '\\u000D'
'\\t''\u0009'Табуляция (Tab)val char = '\\u0009'
',''\u002C'Запятая (Comma)val char = '\\u002C'
'.''\u002E'Точка (Period)val char = '\\u002E'
';''\u003B'Точка с запятой (Semicolon)val char = '\\u003B'
':''\u003A'Двоеточие (Colon)val char = '\\u003A'
'!''\u0021'Восклицательный знак (Exclamation Mark)val char = '\\u0021'
'?''\u003F'Вопросительный знак (Question Mark)val char = '\\u003F'
'"''\u0022'Кавычка (Double Quote)val char = '\\u0022'
'\\'''\u0027'Одинарная кавычка (Single Quote)val char = '\\u0027'
'-''\u002D'Дефис (Hyphen)val char = '\\u002D'
'_''\u005F'Подчеркивание (Underscore)val char = '\\u005F'
'(''\u0028'Открывающая скобка (Left Parenthesis)val char = '\\u0028'
')''\u0029'Закрывающая скобка (Right Parenthesis)val char = '\\u0029'
'[''\u005B'Открывающая квадратная скобка (Left Square Bracket)val char = '\\u005B'
']''\u005D'Закрывающая квадратная скобка (Right Square Bracket)val char = '\\u005D'
'{''\u007B'Открывающая фигурная скобка (Left Curly Brace)val char = '\\u007B'
'}''\u007D'Закрывающая фигурная скобка (Right Curly Brace)val char = '\\u007D'
'☺''\u263A'Улыбка (Smiley Face)val char = '\\u263A'
'©''\u00A9'Знак авторского права (Copyright Symbol)val char = '\\u00A9'
'®''\u00AE'Знак зарегистрированной торговой марки (Registered Trademark)val char = '\\u00AE'
'✓''\u2713'Галочка (Check Mark)val char = '\\u2713'
'✗''\u2717'Крестик (Cross Mark)val char = '\\u2717'
'←''\u2190'Стрелка влево (Left Arrow)val char = '\\u2190'
'→''\u2192'Стрелка вправо (Right Arrow)val char = '\\u2192'
'♥''\u2665'Сердце (Heart)val char = '\\u2665'
CharSequence

Представляет собой последовательность символов. Он является более общим типом, чем String, и предоставляет методы для работы с текстом. Это интерфейс, который не хранит данные сам по себе, а описывает поведение объектов, содержащих текст.

val text: CharSequence = "Kotlin is great!"
length

Возвращает длину последовательности символов.

val text: CharSequence = "Hello, Kotlin"
println("Length: ${text.length}") // Выведет: Length: 13
get(index: Int)

Возвращает символ по заданному индексу.

val text: CharSequence = "Hello"
println("Character at index 1: ${text.get(1)}") // Выведет: Character at index 1: e
subSequence(startIndex: Int, endIndex: Int)

Возвращает подстроку с указанного начального индекса до конечного (не включая его).

val text: CharSequence = "Kotlin Programming"
val subText = text.subSequence(0, 6)
println("Subsequence: $subText") // Выведет: Subsequence: Kotlin
toString

Преобразует CharSequence в строку String.

val text: CharSequence = StringBuilder("Mutable text")
val str: String = text.toString()
println("String representation: $str") // Выведет: String representation: Mutable text
String
https://kotlinlang.org/docs/strings.html

Класс для работы со строками.

val name = "Котлин"
println("Привет, $name!") // Привет, Котлин
Number

базовый класс для числовых типов.

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
Округлить число
val value: Double = 3.14123456789
val roundedValue: Double = value
    .toBigDecimal()
    .setScale(2, RoundingMode.HALF_EVEN)
    .toDouble() // 3.14
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

Метод, который вызывается при доступе к свойству для его чтения. В Kotlin геттеры создаются автоматически для всех свойств, но их можно переопределить, чтобы выполнить дополнительные действия или вернуть другое значение.

• Добавить логику при доступе к переменной

var age: Int = 25
    get() {
        println("Геттер вызывается")
        return field
    }

• Использовать field

var name: String = "John"
    get() {
        return field.toUpperCase()
    } 

• Свойства val

val birthYear: Int = 1990
    get() = field + 1

• Переопределение геттера в производном классе

open class User {
    open val age: Int = 25
}

class AdultUser : User() {
    override val age: Int
        get() = super.age + 10
}

• Определение геттера в интерфейсе

interface Person {
    val age: Int
        get() = 18
}

• Применение геттера с Backing Field

// Применение геттеров с Backing Field
class User {
    private var _name: String? = null
    val name: String
        get() = _name ?: "No name provided"
}
set

Метод, который вызывается при присваивании значения свойству. В Kotlin сеттеры создаются по умолчанию только для изменяемых свойств (var).

• Логика при присвоении значения переменной

var age: Int = 25
    set(value) {
        println("Сеттер вызывается")
        field = value
    }

• Использование field

var age: Int = 25
    set(value) {
        field = value // `field` ссылается на свойство `age`
    }

• Валидация данных в сеттере

var age: Int = 25
    set(value) {
        if (value >= 0) {
            field = value
        } else {
            println("Возраст не может быть отрицательным")
        }
    }

• Переопределение сеттера в производном классе

open class User {
    open var age: Int = 25
}

class AdultUser: User() {
    override var age: Int = 30
        set(value) {
            if (value >= 18) {
                field = value
            } else {
                println("Возраст должен быть не меньше 18")
            }
        }
}

• Определение сеттера в интерфейсе

interface Person {
    var age: Int
        set(value) // Интерфейс определяет, что должно быть свойство с сеттером
}

class User : Person {
    override var age: Int = 18
        set(value) {
            field = value
        }
}

• Применение сеттеров с Backing Field

class User {
    private var _name: String = "John"
    var name: String
        get() = _name
        set(value) {
            _name = value.trim() // Устанавливаем значение, предварительно удалив лишние пробелы
        }
}
field

Используется внутри геттеров и сеттеров для обращения к свойству класса. Оно представляет бэкэнд-поле свойства, которое используется для хранения значения.

class Product {
    var price: Double = 0.0
        get() = field * 1.2 // Добавляем наценку 20%
}

fun main() {
    val product = Product()
    product.price = 100.0
    println(product.price) // Выведет: 120.0
}
Exceptions
https://kotlinlang.org/docs/exceptions.html

Исключения — это события, которые нарушают нормальное выполнение программы, возникающие в результате ошибок во время выполнения, таких как деление на ноль, выход за пределы массива или работа с недоступными ресурсами. Kotlin использует механизмы обработки исключений, такие как try, catch и finally, для управления этими ситуациями и обеспечения устойчивости приложения.

try {
    // Код, генерирующий исключение
} catch (e: Exception) {
    // Обработка исключения
} finally {
    // Постобработка
}
Throwable

Корневой класс всех исключений и ошибок в Kotlin (как и в Java). Он разделяется на два подкласса: Error и Exception.

Throwable
├── Error
│   ├── StackOverflowError
│   └── OutOfMemoryError
│
└── Exception
    ├── RuntimeException
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   └── IndexOutOfBoundsException
    ├── IOException
    └── SQLException
Error

Этот класс представляет собой серьезные ошибки, от которых, как правило, нельзя восстановиться. Ошибки этого типа не нужно обрабатывать в коде, так как они указывают на проблемы на уровне JVM или системы.

OutOfMemoryError – возникает, когда программа превышает допустимый объём памяти.

StackOverflowError – возникает, когда программа уходит в бесконечную рекурсию.

Exception

Этот класс представляет собой обычные исключения, которые могут возникнуть в ходе выполнения программы. Эти исключения можно обрабатывать с помощью блоков try-catch.

RuntimeException - Представляет исключения времени выполнения, которые возникают из-за ошибок логики программы.

NullPointerException - Возникает при попытке обращения к объекту с null значением.

IndexOutOfBoundsException - Выбрасывается при доступе к элементам коллекции или массива за пределами их границ.

IllegalArgumentException - Возникает, когда методу передан аргумент, не соответствующий его ожиданиям.

IOException - Представляет исключения, которые связаны с ошибками ввода/вывода (например, при работе с файлами или сетевыми соединениями).

SQLException - Указывает на ошибки, связанные с работой с базами данных.

FileNotFoundException - Возникает, когда указанный файл не может быть найден.

try

Используется для оборачивания кода, который может выбросить исключение. Это блок, в котором выполняется потенциально «опасный» код.

• Требует обязательного наличия блоков catch или finally.

try {
    val result = 10 / 0
} catch (e: ArithmeticException) {
    println("Ошибка деления на ноль")
} finally {
    println("Завершение операции")
}
try {
    val result = 10 / 0
} catch (e: ArithmeticException) {
    println("Ошибка деления на ноль")
}
try {
    val result = 10 / 5
} finally {
    println("Завершение операции")
}
catch

Перехватывает исключения, возникшие внутри try. В catch можно указать конкретный тип исключения, который нужно обработать, или использовать общий тип Exception.

• Можно указать сколько угодно блоков catch.

try {
    val numbers = listOf(1, 2, 3)
    println(numbers[5]) // IndexOutOfBoundsException
    val result = 10 / 0 // ArithmeticException
} catch (e: ArithmeticException) {
    println("Ошибка: деление на ноль.")
} catch (e: IndexOutOfBoundsException) {
    println("Ошибка: индекс вне границ списка.")
} catch (e: Exception) {
    println("Произошло общее исключение: ${e.message}")
}
finally

Этот блок выполняется всегда, независимо от того, было исключение или нет. Обычно его используют для освобождения ресурсов, закрытия файлов или завершения каких-то операций.

var inputStream: FileInputStream? = null
try {
    inputStream = FileInputStream(fileName)
    inputStream.readBytes()
} catch (e: IOException) {
    println("Ошибка")
} finally {
    inputStream?.close()
}
throw

Используется для явного выбрасывания исключения. Оно позволяет создать и бросить экземпляр класса исключения, что приводит к немедленному прерыванию выполнения текущего блока кода и передаче управления ближайшему блоку catch, если он существует. Имеет тип Nothing.

fun validateAge(age: Int) {
    if (age < 18) {
        throw IllegalArgumentException("Возраст должен быть не меньше 18")
    }
}
@Throws

Используется для явного указания на то, какие исключения могут быть выброшены из функции. Это особенно полезно при работе с Java-кодом, поскольку в Java существуют проверяемые исключения, которые требуют обработки или явного указания в сигнатуре метода. Можно указать одно или несколько исключений, которые могут возникнуть при выполнении функции. Когда такая функция вызывается из Java-кода, IDE и компиляторы будут предупреждать, если это исключение не обрабатывается. Аннотация @Throws не является обязательной в Kotlin, так как языковые конструкции уже позволяют обрабатывать исключения. Однако она полезна для обеспечения совместимости с Java.

@Throws(IllegalArgumentException::class)
fun validateAge(age: Int) {
    if (age < 18) {
        throw IllegalArgumentException("Возраст должен быть не меньше 18")
    }
}
Smart casts
https://kotlinlang.org/docs/typecasts.html
is

Используется для проверки типа объекта во время выполнения. Позволяет проверить, соответствует ли объект определенному типу.

if (obj is SomeType) {
    // Действия, если obj является экземпляром SomeType
}

!is

Противоположный оператор, который проверяет, не является ли объект указанным типом.

fun checkNotString(obj: Any) {
    if (obj !is String) {
        println("This is not a String")
    } else {
        println("This is a String of length ${obj.length}")
    }
}

fun main() {
    checkNotString(100)     // Выведет: This is not a String
    checkNotString("Hello") // Выведет: This is a String of length 5
}
as

Используется для приведения типов. Позволяет явно преобразовать объект к определенному типу. Если объект не может быть приведен к указанному типу, оператор as выбрасывает исключение ClassCastException.

val obj: Any = "Hello, Kotlin"
val str: String = obj as String // Приведение типа к String

as?

Безопасное приведение типа.

val obj: Any = 123
val str: String? = obj as? String // Попытка приведения типа к String
Annotations

Эти аннотации помогают управлять тем, как Kotlin-код будет компилироваться и взаимодействовать с Java-кодом, обеспечивая гибкость и совместимость.

annotation

Используется для создания аннотаций, которые могут добавлять метаданные к классам, функциям, параметрам и другим элементам кода. Аннотации помогают компилятору или сторонним инструментам обрабатывать код.

annotation class Info(val author: String, val version: Int)

@Info(author = "John", version = 1)
class Example
@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
thread

Используется для создания и запуска нового потока. Он является удобной оберткой над классом Thread, позволяя вам легко создавать потоки с минимальными усилиями. Метод позволяет задавать параметры, такие как name, start, daemon, и contextClassLoader, чтобы настроить новый поток.

fun main() {
    thread {
        // Код выполняется в новом потоке
        println("Running in a separate thread")
    }
    println("Running in the main thread")
}
@Synchronized

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

// В этом примере методы increment и getCount синхронизированы, что обеспечивает корректное обновление и чтение значения count из разных потоков.

class Counter {
    private var count = 0

    @Synchronized
    fun increment() {
        count++
    }

    @Synchronized
    fun getCount(): Int {
        return count
    }
}

var value: Int = 0
    @Synchronized get() = 5
    @Synchronized set(value) { field = value }
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 лучше понимать намерения разработчика и улучшать поведение кода за счет более точного анализа. Они могут быть полезны в ряде случаев, например, для корректной работы с условными выражениями, которые зависят от результата вызова функции, или для уточнения условий, которые должны выполняться для корректной работы кода.

• Контракт для проверки null

// В этом примере contract указывает, что если функция requireNotNull не выбрасывает исключение, то её результат не может быть null. 
// Это помогает компилятору понимать, что после вызова requireNotNull(x) переменная y не может быть 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
}

• Контракт для условного выражения

// В этом примере контракт сообщает компилятору, что если функция isEmptyOrNull возвращает true, то list либо пустой, либо null. 
// Это позволяет компилятору понять, что если isEmptyOrNull возвращает true, то переменная list будет либо пустой, либо 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
    }
}
DSL
https://kotlinlang.org/docs/type-safe-builders.html

Kotlin DSL (Domain-Specific Language) — это способ создания мини-языков, ориентированных на конкретную предметную область, с помощью синтаксических возможностей Kotlin. DSL позволяет сделать код более читаемым и интуитивно понятным для разработчиков, предоставляя специфичный синтаксис для определённых задач. Примеры: Gradle Kotlin DSL и Jetpack Compose.

class Pizza {
    var dough: String = ""
    var sauce: String = ""
    var toppings: MutableList<String> = mutableListOf()

    fun topping(name: String) {
        toppings.add(name)
    }

    override fun toString(): String {
        return "Pizza with $dough dough, $sauce sauce, and toppings: $toppings"
    }
}

fun pizza(init: Pizza.() -> Unit): Pizza {
    val pizza = Pizza()
    pizza.init()
    return pizza
}

val myPizza = pizza {
    dough = "Thin crust"
    sauce = "Tomato"
    topping("Cheese")
    topping("Pepperoni")
}

println(myPizza)
Расширение синтаксиса

Kotlin предоставляет мощные функции, такие как функции расширения, лямбды с приемником, именованные аргументы, что делает возможным создание удобных DSL.

fun String.hasLength(length: Int) = this.length == length
Лямбды с приемником

Используются для создания контекста, в котором вызываются методы и свойства объекта без необходимости постоянно указывать его. Это позволяет строить цепочки вызовов.

class HtmlTag(val name: String) {
    fun render() = "<$name></$name>"
}

fun html(init: HtmlTag.() -> Unit): HtmlTag {
    val tag = HtmlTag("html")
    tag.init()
    return tag
}

val result = html {
    // Внутри этой лямбды мы можем вызывать методы HtmlTag напрямую
}
Именованные аргументы и параметры по умолчанию

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

fun buildHouse(floors: Int = 2, color: String = "White") {
    println("House with $floors floors and $color color")
}

buildHouse(floors = 3, color = "Blue")
Вопросы на собесе (82)
  • Variables (14)
    1. Разница между val и var?

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

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

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

    1. В чем преимущество использования val вместо var?

      Использование val вместо var обеспечивает неизменяемость, что делает код безопаснее и облегчает его понимание. Это помогает избежать непреднамеренных изменений данных, снижая вероятность ошибок.

    1. Что будет если вызвать lateinit var до объявления?

      Произойдет UninitializedPropertyAccessException.

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

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

    1. Для чего используется const?

      Для объявления неизменяемых констант, которые известны на этапе компиляции. Могут быть использованы в качестве значений по умолчанию и в аннотациях.

    1. Какие типы данных поддерживает const?

      Int Long Double Float Boolean Char String

    1. Можно ли в const добавить List?

      Нет. Тип должен быть известен на этапе компиляции и быть неизменяемым.

    1. Где можно объявлять константу?

      На уровне файла (top-level) вне классов.

      Внутри object.

    1. Где хранятся переменные const val?

      На уровне байткода Java как static final поля в сгенерированном классе. Они компилируются как статические константы и хранятся в метаспейс (метаданные) JVM, что позволяет к ним быстро обращаться без создания экземпляра класса. Благодаря этому значению const val можно обращаться напрямую по имени класса, так как они статические и глобально доступны.

    1. Есть ли в Kotlin возможность объявить поле, чтобы оно скомпилировалось в Java-примитив?

      Да. Для этого нужно использовать типы Int, Long, Double, Float, Char, Boolean, Byte, и Short — Kotlin автоматически скомпилирует их в соответствующие Java-примитивы, если это возможно. Например, val number: Int = 10 скомпилируется в int в байткоде Java. Однако если поле nullable (например, Int?), оно будет скомпилировано как Integer в Java, чтобы поддерживать значение null.

    1. Каким правилам должна соответствовать переменная, чтобы стать Java-примитивом?

      Для компиляции переменной в Java-примитив она должна быть ненуллабельной и одного из примитивных типов Kotlin, таких как Int, Boolean, Double и т.д. При этом переменная не должна использоваться в контексте, где требуется объектный тип, например, при использовании Int?.

    1. Можно ли изменить переменную типа val?

      Переменную типа val нельзя изменить после инициализации. Она является read-only, что означает, что после присвоения значения вы не можете присвоить новое значение этой переменной. Однако если переменная val содержит изменяемый объект, например, список или массив, вы можете изменять содержимое этого объекта, но не можете переназначить саму переменную.

      val myList = mutableListOf(1, 2, 3)
      myList.add(4) // Допустимо, изменяем содержимое списка
    1. Где неверно создана переменная?

      val str: String = null

      var num = 50;

      var number: Float = 45.001f

      var isGet: Boolean? = null

      var char = 'S'

  • Types (3)
    1. Какие типы есть в Kotlin?

      Числовые: Int Long Double Float Byte Short

      Символы: Char

      Логический: Boolean

      Строки: String

      Специальные: Any Unit Nothing

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

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

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

      Array

      Object

      Int

      List

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

  • Basic Types (8)
    1. Что такое Any?

      Базовый тип для всех классов в Kotlin.

    1. Какие методы есть у класса Any?

      equals() hashCode() toString()

    1. Разница между Any в Kotlin и Object в Java?

      Any содержит только 3 метода: toString, equals, и hashCode. В отличие от Object, Any не включает методы, связанные с многопоточностью, такие как wait, notify, и notifyAll. В Kotlin такие методы предоставляются через kotlin.concurrent или в других библиотеках, что делает Any более легковесным и подходящим для Kotlin-экосистемы.

    1. Что такое Unit?

      Тип для функций без возвращаемого значения, аналог void в Java.

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

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

    1. Как Nothing устроен в Kotlin?

      Класс с приватным конструктором.

    1. Почему Unit в Kotlin существует в единственном экземпляре?

      В Kotlin существует только один экземпляр класса Unit. Это Singleton, представляющий отсутствие возвращаемого значения в функциях или пустое значение. Все функции, которые не возвращают ничего, фактически возвращают этот единственный объект Unit.

    1. Сколько экземпляров класса Unit может существовать?

      Ни одного.

      Три.

      Один.

      Сколько угодно.

  • Modifiers (6)
    1. Какие есть модификаторы доступа в Kotlin? В чем отличие от Java?

      public private protected internal

    1. Разница между модификаторами в Kotlin и Java?

      В Kotlin internal ограничивает доступ в пределах модуля, а в Java эквивалентный модификатор отсутствует.

    1. Для чего явно используется модификатор final в Kotlin?

      Запрещает повторное переопределение метода в дочернем классе.

    1. Для чего используется ключевое слово open?

      Используется для того, чтобы разрешить наследование от класса или переопределение метода/свойства, поскольку по умолчанию классы и их члены закрыты для наследования.

    1. Можно ли объявить конструкцию private open class Person?

      Да.

    1. Какие существуют модификаторы доступа у элементов класса?

      public доступен из любого места. Это значение по умолчанию.

      private доступен только внутри самого класса или файла (если используется на уровне файла).

      protected доступен только внутри самого класса и его подклассов. Не может использоваться для элементов на уровне файла.

      internal доступен внутри модуля, то есть коду, который компилируется вместе (например, в одном проекте или библиотеке).

      open позволяет методу быть унаследованным или переопределенным. По умолчанию все классы и методы в Kotlin являются final, если не указано иное.

      final запрещает наследование класса или переопределение метода. Используется по умолчанию для классов и методов, если не указан модификатор open.

  • Exceptions (10)
    1. В чем отличие исключений в Kotlin и Java?

      В Kotlin нет проверенных исключений, и вместо обязательной обработки ошибок можно использовать функциональные подходы с Result. Исключения генерируются и обрабатываются аналогично Java, но без требований к проверке исключений.

    1. Есть ли в Kotlin проверяемые исключения?

      В Kotlin нет проверяемых исключений (checked exceptions), в отличие от Java. Все исключения являются непроверяемыми (unchecked), что означает, что компилятор не требует обработки или объявления исключений в сигнатуре функции. Это упрощает код и делает его более лаконичным, однако требует от разработчиков большей ответственности за обработку возможных ошибок.

    1. Как обрабатываются исключения в Kotlin?

      Исключения в Kotlin обрабатываются с помощью блоков try-catch-finally. Внутри блока try выполняется код, который может вызвать исключение. Если исключение возникает, управление передается в соответствующий блок catch. Блок finally выполняется в любом случае после завершения try или catch, и используется для освобождения ресурсов.

    1. Какая иерархия у исключений в Kotlin?

      В Kotlin иерархия исключений основана на классе Throwable, который является корневым классом. Он делится на две основные категории:

      Error - ошибки, указывающие на серьезные проблемы, с которыми приложение не может справиться (например, OutOfMemoryError).

      Exception - исключения, которые могут быть обработаны программой: RuntimeException: Исключения времени выполнения, (например, NullPointerException, IndexOutOfBoundsException, IllegalArgumentException) и Checked exceptions: Исключения, которые должны быть обработаны или объявлены в сигнатуре функции (например, IOException).

    1. В чем принципиальная разница между Error и Exception?

      Error обозначает серьезные проблемы, обычно вне контроля приложения, тогда как Exception относится к условиям, которые можно предсказать и обработать для повышения устойчивости.

    1. Приведи примеры исключений типа Error в Kotlin?

      OutOfMemoryError

      StackOverflowError

    1. Для чего в конструкции обработки исключений нужен блок finally?

      Блок finally выполняется независимо от того, произошло ли исключение, что позволяет гарантировать выполнение кода, например, для освобождения ресурсов, таких как закрытие потоков или соединений.

    1. Может ли конструкция try-catch-finally не содержать блок try?

      Нет.

    1. Может ли конструкция try-catch-finally не содержать блок catch?

      Да.

    1. Сколько блоков catch может содержать конструкция try-catch-finally?

      Сколько угодно.

    1. Переменной A присвоен блок try-finally, в try значение устанавливается = 10 в finally = 20, чему будет равно А?

      Переменная A будет равна 10, поскольку значение в блоке try присваивается переменной до выполнения блока finally. Блок finally выполняется после, но он не изменяет значение переменной A.

  • init (4)
    1. Сколько блоков init можно объявить в классе?

      Сколько угодно.

    1. В каком порядке выполняются код в нескольких блоках init?

      В порядке их объявления в классе.

    1. Где код выполнится быстрее, в блоке init или в secondary constructor?

      Код выполнится быстрее в блоке init, так как он вызывается сразу после первичного конструктора.

    1. Можно ли объявить блок init внутри object?

      Да, блок init можно объявить внутри object, и он выполнится при первой инициализации этого объекта.

  • Class (5)
    1. Какие классы существуют в Kotlin?

      class

      data class

      value class

      sealed class

      enum class

      inner class

      nested class (просто класс внутри класса).

    1. Разница между class в Kotlin и Java?

      В Kotlin классы по умолчанию final, в Java - public.

      Kotlin поддерживает первичные и вторичные конструкторы; Java — только явные.

      В Kotlin есть data class с автоматической реализацией методов.

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

      == сравнивает значения (эквивалент equals в Java).

      === сравнивает ссылки (проверяет, указывают ли обе ссылки на один и тот же объект).

    1. Как сравнить ссылки на классы?

      С помощью оператора ===. Этот оператор проверяет, ссылаются ли две переменные на один и тот же объект в памяти.

    1. Что выводит метод toString() у обычного класса?

      По умолчанию метод toString() у обычного класса в Kotlin выводит строку, состоящую из имени класса и хеша объекта в формате, похожем на: ClassName@hashcode.

  • Functions (7)
    1. Чем отличается передача параметров в функцию в Kotlin и в Java?

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

      В Java можно работать с полями функции, присваивая им новые значения в теле функции.

    1. Как в Kotlin передаются параметры функции по ссылке или по значению?

      По значению. Передаётся копия ссылки на объект, а не сам объект. Поэтому, если объект изменяемый (например, MutableList), то можно изменить его содержимое внутри функции. Но если изменить саму ссылку (например, присвоить новый объект), то это не отразится на исходной переменной.

    1. Если передать в функцию класс с var-полями можно ли будет поменять значение полей?

      Да, если передать в функцию объект класса с var-полями, можно изменить значения этих полей, так как объект передается по ссылке.

    1. Что такое функция высшего порядка?

      Это функция, которая принимает другую функцию в качестве аргумента или возвращает функцию в качестве результата.

    1. Что такое анонимная функция?

      Анонимная функция — это функция без имени, используемая на месте для кратковременных операций.

    1. Напиши результат вывода на экран 5 закомментированных выражений, в случае любой ошибки в ответе напиши Exception?
      fun transform(x: Int): Int = x * 2
      
      fun main() {
          val numbers = listOf(1, 2, 3)
      
          // println("1: " + numbers.map(::transform))
          // println("2: " + numbers.map { ::transform })
          // println("3: " + numbers.map { transform(it) })
          // println("4: " + numbers.map { _ -> ::transform })
          // println("5: " + numbers.map { x -> transform(x) })
      }

      Ответы
      1 -
      [2, 4, 6]
      2 -
      [Function1, Function1, Function1]
      3 -
      [2, 4, 6]
      4 -
      [Function1, Function1, Function1]
      5 -
      [2, 4, 6]

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

      fun name(args: List<Any>)

      fun name(vararg args: Any)

      fun name(args: Any...)

      fun name(args: Array<Any>)

  • Enum (1)
    1. Какие типы данных можно положить в enum?

      Int Long Float Double Boolean Char и другие.

      String.

      class data class.

      Объекты другого enum class.

      Lambdas.

  • Lambdas (3)
    1. Что такое лямбда в Kotlin?

      Это анонимная функция, которая может быть присвоена переменной или передана в качестве аргумента другой функции. Она может захватывать переменные из своего окружения и используется для функционального программирования. Лямбда-выражения позволяют писать более лаконичный и выразительный код.

    1. В каком из разделов памяти хранятся лямбды?

      Лямбды в Kotlin хранятся в разделе памяти, называемом heap (куча). При создании лямбды для хранения ее состояния и захваченных переменных, создается объект, который размещается в куче.

    1. Есть ли equals и hashcode у лямбды в Kolin?

      Есть только equals().

      Есть обе функции.

      Нет ни одной.

  • Анонимный класс (3)
    1. Что такое анонимный класс?

      Это класс, который не имеет имени и создается на месте, обычно для реализации интерфейсов или абстрактных классов. Он позволяет создавать объекты с переопределенными методами без явного определения нового класса. Такой подход удобен для краткосрочного использования и уменьшает количество кода.

    1. Можно ли создать инстанс анонимного класса?

      Да, можно создать инстанс анонимного класса, используя синтаксис, который реализует интерфейс или наследует класс на месте.

    1. Какой недостаток есть у анонимных классов с точки зрения памяти?

      Создание дополнительных объектов со скрытой ссылкой на родительский класс, что может привести к утечкам памяти. Это увеличивает время жизни родительского объекта, даже если он больше не нужен, особенно в долгоживущих потоках.

  • DSL (3)
    1. Что такое DSL в Kotlin?

      Способ создания мини-языков, специфичных для определенной задачи. Он позволяет писать код, который выглядит как набор деклараций, что упрощает работу с библиотеками или фреймворками.

    1. Какие есть примеры использования DSL?

      Gradle Kotlin DSL для настройки проектов.

      Jetpack Compose.

      HTML Builders: Создание HTML-документов.

      Exposed: Работа с базами данных через декларативные SQL-запросы.

    1. Какие конструкции поддерживает DSL?

      Лямбды с приемником: позволяют вызывать функции объекта без явного указания его имени.

      Инфиксные функции: позволяют писать более читаемые выражения.

      Расширения: добавляют методы к существующим классам для улучшения API.

      Именованные аргументы: улучшают читаемость, особенно для сложных вызовов функций.

      Операторы перегрузки: позволяют использовать синтаксический сахар для улучшения выразительности.

  • Concurrent (2)
    1. Почему synchronized в Kotlin сделан через аннотацию @Synchronized?

      @Synchronized в Kotlin реализован как аннотация для упрощения синхронизации методов, делая код более читаемым. Это позволяет разработчикам легко добавлять потокобезопасность, избегая ручного управления синхронизацией и снижая вероятность ошибок.

    1. Будет ли данный код работать корректно?
      @Synchronized
      fun fib(x: Int): Int = if (x < 2) x else fib(x - 1) + fib(x - 2)

      Код будет работать корректно с точки зрения потокобезопасности благодаря аннотации @Synchronized. Однако, из-за рекурсивной реализации без мемоизации или оптимизаций, при больших значениях x он будет крайне неэффективен и может привести к переполнению стека.

  • Другие (13)
    1. Чем Kotlin отличается от Java?

      Kotlin предлагает лаконичный синтаксис, поддержку нулевой безопасности, корутины для асинхронного программирования и расширенные функции, такие как свойства и функции расширения, которые отсутствуют в Java. Кроме того, Kotlin полностью совместим с Java и предоставляет современную альтернативу с улучшенной выразительностью и безопасностью.

      Null-safety.

      Именованные аргументы.

      Выведение типов.

      Extension-функции.

      Локальные функции.

      Операторы перегрузки.

      Data-классы.

      Sealed-классы.

      DSL (Domain-Specific Languages).

    1. В чем преимущество Kotlin в Andrid-разработке? (Какие фичи нравятся)

      Kotlin упрощает разработку Android-приложений благодаря своей лаконичности, современным языковым конструкциям и полной совместимости с Java. Также он предоставляет встроенную поддержку для безопасной работы с нулевыми значениями и упрощает работу с корутинами.

    1. Что не нравится в Kotlin?

      Высокая функциональность.

      Избыточное использование scope-функций.

      Более длительное время компиляции по сравнению с Java.

    1. Что такое null safety в Kotlin? Как осуществить проверку на null?

      Null safety предотвращает ошибки с null. Проверку на null можно выполнить с помощью безопасного вызова ?., оператора Элвиса ?: или оператором !! для выброса исключения, если значение null.

    1. Для чего используется аннотация @JvmOverloads?

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

    1. Чем if в Kotlin отличается от if в Java?

      В Kotlin if является выражением, возвращающим значение, что позволяет использовать его в присваивании.

    1. Зачем нужны Kotlin Contracts?

      Kotlin contracts описывают поведение функций, уточняя их влияние на контроль потока и гарантии о параметрах или результатах, что позволяет компилятору лучше анализировать и оптимизировать код. Это помогает избежать ошибок и улучшить работу с nullable-типами.

    1. Как отработает функция main?
      class A {
      		lateinit var first: Model
      		var second: Model? = null
      }
      
      fun main() {
      		val a = A()
      		a.first.get()
      		a.second?.get()
      }

      a.first.get() вызовет исключение UninitializedPropertyAccessException, так как переменная first имеет модификатор lateinit, но не была инициализирована до обращения к ней.

    1. Что будет выведено в результате выполнения кода?
      var num = 9
      num++
      num += 6
      --num
      println(num)

      15

    1. Компилируется ли этот код? Как исправить?
      fun main() {
      		foo1 {
      				println("Hello World")
      				return
      		}
      }
      
      fun foo1(f: () -> Unit) {
      		f()
      }

      Код не компилируется из-за того, что оператор return в лямбда-функции пытается вернуть значение из функции main, а не из лямбды. Чтобы исправить это, можно заменить return на return@foo1, чтобы явно указать, что нужно выйти из функции foo1.

    1. Компилируется ли этот код? Как исправить? Как исправить с помощью inline?
      fun bar() {
          foo {
              println(1)
              return
              println(2)
          }
          println(3)
      }
      
      fun foo(fooLambda: () -> Unit) {
          fooLambda()
      }

      Код не компилируется из-за того, что оператор return в лямбда-функции пытается вернуть значение из функции bar, а не из foo. Чтобы исправить это, нужно использовать return@foo вместо просто return. Чтобы исправить код с использованием inline, можно сделать функцию foo инлайновой. Это позволит использовать return в лямбде для выхода из родительской функции bar. Код будет компилироваться и выполняться правильно, выводя 1 и 3, при этом return в лямбде завершает выполнение функции bar.

    1. Каким будет результат функции main?
      class SpecialFunction: () -> Unit {
          override fun invoke() {
              println("Invoked from an instance.")
          }
      }
      
      fun main() {
          try { SpecialFunction()() }
          catch (ex: Exception) { println("An error occurred") }
      }

      Ответ: Invoked from an instance.

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

      На базе Android Studio

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

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

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

      На базе Java Virtual Machine