LEARN X · ЗА 16 МИН

Kotlin

Kotlin за 16 минут: весь язык на одной странице — val/var, null-безопасность, when, коллекции, классы, data class, sealed и scope-функции.

Kotlin — современный, лаконичный и null-безопасный язык от JetBrains. Компилируется в байткод JVM (а также в JS и нативный код), полностью совместим с Java и стал основным языком для Android. Весь язык — на одной странице: смотри код, всё объяснено прямо в комментариях.

Структура программы

// Однострочный комментарий
/* Многострочный
   комментарий */
/** Документирующий KDoc-комментарий */

// Точка входа в программу — функция main.
// fun — ключевое слово для объявления функции.
fun main() {
    println("Привет, Kotlin!")   // печать со переводом строки -> Привет, Kotlin!
    print("без перевода строки")  // печать без \n
    // Точка с запятой в конце строк не нужна.
}

Переменные

fun main() {
    // val — неизменяемая (read-only), как final в Java.
    val name = "Аня"
    // name = "Боря"   // ОШИБКА компиляции: val переназначить нельзя

    // var — изменяемая переменная.
    var age = 25
    age = 26          // OK

    // Тип выводится автоматически, но можно указать явно:
    val pi: Double = 3.14
    val count: Int = 10
    val big: Long = 10_000_000_000   // подчёркивания для читаемости
    val flag: Boolean = true
    val letter: Char = 'A'
    val small: Byte = 1

    println("$name, $age, $pi, $count, $big, $flag, $letter, $small")
    // -> Аня, 26, 3.14, 10, 10000000000, true, A, 1
}

Строки

fun main() {
    val name = "Мир"

    // Шаблоны строк: $переменная и ${выражение}
    println("Привет, $name!")            // -> Привет, Мир!
    println("Длина: ${name.length}")     // -> Длина: 3
    println("2 + 2 = ${2 + 2}")          // -> 2 + 2 = 4

    // Многострочные строки через тройные кавычки.
    val text = """
        Первая строка
        Вторая строка
    """.trimIndent()   // trimIndent убирает общий отступ
    println(text)

    // Полезные методы строк:
    println("kotlin".uppercase())        // -> KOTLIN
    println("KOTLIN".lowercase())        // -> kotlin
    println("  hi  ".trim())             // -> hi
    println("a,b,c".split(","))          // -> [a, b, c]
    println("abc".reversed())            // -> cba
    println("kotlin".contains("lin"))    // -> true
    println("kotlin".replace("k", "K"))  // -> Kotlin
    println("abc"[0])                    // -> a (доступ по индексу)
}

Null-безопасность

Главная фишка Kotlin: типы по умолчанию не могут быть null. Чтобы разрешить null, добавь ? к типу.

fun main() {
    // Обычный тип не принимает null:
    var a: String = "текст"
    // a = null            // ОШИБКА компиляции

    // Nullable-тип помечается знаком "?"
    var b: String? = "текст"
    b = null              // OK

    // ?. — безопасный вызов: вернёт null, если объект null (без падения)
    println(b?.length)    // -> null (а НЕ NullPointerException)

    val c: String? = "Kotlin"
    println(c?.length)    // -> 6

    // ?: — оператор Элвиса: значение по умолчанию, если слева null
    val len = b?.length ?: 0
    println(len)          // -> 0

    // !! — утверждаем "точно не null" (бросит NPE, если ошиблись)
    val d: String? = "ok"
    println(d!!.length)   // -> 2

    // Безопасная цепочка вызовов:
    val upper = b?.trim()?.uppercase() ?: "ПУСТО"
    println(upper)        // -> ПУСТО
}

Операторы и условия

fun main() {
    // Арифметика: + - * / %  ;  сравнения: == != < > <= >=
    // Логика: && (и), || (или), ! (не)
    println(7 / 2)    // -> 3 (целочисленное деление)
    println(7 % 2)    // -> 1 (остаток)
    println(7.0 / 2)  // -> 3.5

    // if — это ВЫРАЖЕНИЕ, оно возвращает значение (заменяет тернарный оператор).
    val x = 10
    val sign = if (x > 0) "плюс" else if (x < 0) "минус" else "ноль"
    println(sign)     // -> плюс

    // when — мощная замена switch. Тоже выражение.
    val grade = 4
    val text = when (grade) {
        5 -> "отлично"
        4 -> "хорошо"
        3 -> "удовлетворительно"
        in 1..2 -> "плохо"        // диапазон
        else -> "неизвестно"
    }
    println(text)     // -> хорошо

    // when без аргумента — как цепочка if/else:
    val n = 15
    val fizz = when {
        n % 15 == 0 -> "FizzBuzz"
        n % 3 == 0  -> "Fizz"
        n % 5 == 0  -> "Buzz"
        else        -> "$n"
    }
    println(fizz)     // -> FizzBuzz
}

Циклы

fun main() {
    // Диапазоны: 1..5 включает оба конца
    for (i in 1..5) print("$i ")          // -> 1 2 3 4 5
    println()

    // until — без верхней границы
    for (i in 0 until 5) print("$i ")     // -> 0 1 2 3 4
    println()

    // downTo — по убыванию; step — шаг
    for (i in 10 downTo 1 step 2) print("$i ")  // -> 10 8 6 4 2
    println()

    // Перебор коллекции
    val fruits = listOf("яблоко", "груша", "слива")
    for (f in fruits) print("$f ")        // -> яблоко груша слива
    println()

    // С индексом
    for ((i, f) in fruits.withIndex()) {
        println("$i: $f")                 // -> 0: яблоко ...
    }

    // while и do-while
    var n = 3
    while (n > 0) { print("$n "); n-- }   // -> 3 2 1
    println()

    // break и continue работают как обычно (есть и метки@)
    for (i in 1..10) {
        if (i == 3) continue
        if (i == 5) break
        print("$i ")                      // -> 1 2 4
    }
    println()
}

Коллекции

fun main() {
    // listOf — неизменяемый список
    val nums = listOf(1, 2, 3, 4, 5)
    println(nums[0])          // -> 1
    println(nums.size)        // -> 5
    println(nums.first())     // -> 1
    println(nums.last())      // -> 5

    // mutableListOf — изменяемый список
    val list = mutableListOf("a", "b")
    list.add("c")
    list.removeAt(0)
    println(list)             // -> [b, c]

    // Set — множество (без дубликатов)
    val set = setOf(1, 1, 2, 3)
    println(set)              // -> [1, 2, 3]

    // Map — словарь (ключ -> значение)
    val ages = mapOf("Аня" to 25, "Боря" to 30)
    println(ages["Аня"])      // -> 25
    val mutable = mutableMapOf("x" to 1)
    mutable["y"] = 2
    println(mutable)          // -> {x=1, y=2}

    // Функциональные операции:
    println(nums.map { it * 2 })        // -> [2, 4, 6, 8, 10]
    println(nums.filter { it % 2 == 0 })// -> [2, 4]
    println(nums.sum())                 // -> 15
    println(nums.maxOrNull())           // -> 5
    println(nums.any { it > 4 })        // -> true
    println(nums.all { it > 0 })        // -> true
    // Цепочки: чётные -> в квадрат
    println(nums.filter { it % 2 == 0 }.map { it * it })  // -> [4, 16]
}

Функции

// Обычная функция: параметры с типами, тип возврата после ":"
fun sum(a: Int, b: Int): Int {
    return a + b
}

// Однострочная функция (выражение) — тип выводится сам
fun mul(a: Int, b: Int) = a * b

// Аргументы по умолчанию
fun greet(name: String, greeting: String = "Привет") = "$greeting, $name!"

// Unit — отсутствие возвращаемого значения (как void), можно опустить
fun log(msg: String) {
    println("LOG: $msg")
}

// vararg — переменное число аргументов
fun total(vararg xs: Int): Int = xs.sum()

// Функция-расширение: добавляем метод к существующему типу
fun String.shout() = this.uppercase() + "!"

fun main() {
    println(sum(2, 3))                    // -> 5
    println(mul(4, 5))                    // -> 20
    println(greet("Аня"))                 // -> Привет, Аня!
    // Именованные аргументы (порядок не важен):
    println(greet(greeting = "Хай", name = "Боря")) // -> Хай, Боря!
    println(total(1, 2, 3, 4))            // -> 10
    println("kotlin".shout())             // -> KOTLIN!

    // Лямбды — анонимные функции в фигурных скобках.
    val square = { x: Int -> x * x }
    println(square(6))                    // -> 36
    // it — неявное имя единственного параметра
    listOf(1, 2, 3).forEach { print("$it ") } // -> 1 2 3
    println()
}

Классы и ООП

// Класс с первичным конструктором прямо в заголовке.
// val/var в конструкторе сразу объявляют свойства.
class Person(val name: String, var age: Int) {

    // Дополнительное свойство
    var city: String = "Не указан"

    // Блок init выполняется при создании объекта
    init {
        println("Создан человек: $name")
    }

    // Метод
    fun greet() = "Меня зовут $name, мне $age лет"

    // Свойство с пользовательским геттером (вычисляемое)
    val isAdult: Boolean
        get() = age >= 18
}

fun main() {
    // Объект создаётся БЕЗ ключевого слова new
    val p = Person("Аня", 25)   // -> Создан человек: Аня
    println(p.greet())          // -> Меня зовут Аня, мне 25 лет
    p.age = 26                  // var-свойство можно менять
    p.city = "Москва"
    println(p.age)              // -> 26
    println(p.isAdult)          // -> true
}

Data-классы и наследование

// data class — автоматически даёт toString, equals, hashCode, copy
data class User(val name: String, val age: Int)

// Классы по умолчанию final. Чтобы наследовать — open.
open class Animal(val name: String) {
    open fun sound() = "..."          // open — метод можно переопределить
}

// Наследование через ":"
class Dog(name: String) : Animal(name) {
    override fun sound() = "Гав"      // override обязателен
}

// abstract — нельзя создать напрямую, только наследников
abstract class Shape {
    abstract fun area(): Double       // абстрактный метод без тела
}

class Circle(val r: Double) : Shape() {
    override fun area() = 3.14 * r * r
}

fun main() {
    val u1 = User("Аня", 25)
    val u2 = User("Аня", 25)
    println(u1)                  // -> User(name=Аня, age=25)
    println(u1 == u2)            // -> true (сравнение по значению)
    val u3 = u1.copy(age = 30)   // copy с изменением поля
    println(u3)                  // -> User(name=Аня, age=30)
    // Деструктуризация:
    val (name, age) = u1
    println("$name $age")        // -> Аня 25

    println(Dog("Рекс").sound()) // -> Гав
    println(Circle(2.0).area())  // -> 12.56
}

Интерфейсы и объекты

// interface — контракт; может иметь и методы по умолчанию
interface Clickable {
    fun click()                          // абстрактный
    fun describe() = "Кликабельный"      // метод с реализацией
}

class Button : Clickable {
    override fun click() = println("Клик!")
}

// object — синглтон: единственный экземпляр, создаётся лениво
object Config {
    val version = "1.0"
    fun info() = "Версия $version"
}

// companion object — "статические" члены внутри класса
class Database {
    companion object {
        const val NAME = "main_db"
        fun connect() = "Подключение к $NAME"
    }
}

fun main() {
    val b = Button()
    b.click()                    // -> Клик!
    println(b.describe())        // -> Кликабельный

    // К object-синглтону обращаемся по имени, без создания:
    println(Config.info())       // -> Версия 1.0

    // К companion — через имя класса (как статика в Java):
    println(Database.NAME)       // -> main_db
    println(Database.connect())  // -> Подключение к main_db
}

Sealed-классы и when

Запечатанные классы ограничивают иерархию: компилятор знает все варианты, поэтому when по ним не требует else.

// sealed class — все наследники известны на этапе компиляции
sealed class Result

data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()

// when по sealed-классу исчерпывающий: else не нужен.
// Внутри ветки происходит smart cast к нужному типу.
fun handle(result: Result): String = when (result) {
    is Success -> "Данные: ${result.data}"    // result уже как Success
    is Error   -> "Ошибка: ${result.message}"
    Loading    -> "Загрузка..."
    // else не требуется — варианты перечислены полностью
}

fun main() {
    println(handle(Success("42")))   // -> Данные: 42
    println(handle(Error("сбой")))   // -> Ошибка: сбой
    println(handle(Loading))         // -> Загрузка...
}

Полезное: smart casts и scope-функции

fun describe(obj: Any): String {
    // Smart cast: после проверки is тип сужается автоматически
    if (obj is String) {
        return "Строка длиной ${obj.length}"   // obj уже String
    }
    return "Что-то ещё"
}

fun main() {
    println(describe("привет"))   // -> Строка длиной 6

    // --- Scope-функции ---

    // let — выполнить блок, если объект не null; it — сам объект
    val name: String? = "Kotlin"
    name?.let {
        println("Длина: ${it.length}")    // -> Длина: 6
    }

    // apply — настроить объект; внутри this, возвращает сам объект
    val sb = StringBuilder().apply {
        append("Hello")
        append(", world")
    }
    println(sb)                   // -> Hello, world

    // also — побочное действие; it — объект, возвращает объект
    val nums = mutableListOf(1, 2, 3).also {
        println("Размер: ${it.size}")     // -> Размер: 3
    }

    // run — выполнить блок и вернуть результат; внутри this
    val len = "abc".run { length * 2 }
    println(len)                  // -> 6

    // with — то же, но объект передаётся аргументом
    val upper = with("kotlin") { uppercase() }
    println(upper)                // -> KOTLIN
    println(nums)                 // -> [1, 2, 3]
}
Поддержать проект