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]
}