Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,280 @@
---
title: Функции
description: Объявление функций, значения по умолчанию, именованные аргументы, vararg и локальные функции
tags:
- kotlin
- syntax
---
Функция объявляется словом `fun`. В Kotlin она может жить на верхнем уровне файла, внутри класса или внутри другой функции.
```kotlin
fun greet(name: String): String {
return "Привет, $name"
}
fun main() {
println(greet("Kotlin")) // Привет, Kotlin
}
```
Порядок: `fun` → имя → параметры → `:` тип результата → тело.
## Выражение вместо тела
Если функция - одно выражение, фигурные скобки и `return` убираются:
```kotlin
fun square(x: Int): Int = x * x
fun greet(name: String) = "Привет, $name" // тип выведен: String
fun max(a: Int, b: Int) = if (a > b) a else b
```
Это основная форма в идиоматичном Kotlin. Тип результата можно опустить - компилятор выведет его. Но для публичного API его лучше писать явно: это контракт, и его случайное изменение не должно быть незаметным.
## Unit - отсутствие результата
```kotlin
fun log(message: String): Unit {
println(message)
}
fun log2(message: String) { // : Unit можно опустить
println(message)
}
```
`Unit` - настоящий тип с единственным значением, а не пустота. Подробнее: [Any, Unit, Nothing](../Базовые%20типы%20ООП/Any,%20Unit,%20Nothing.md)
## Параметры
Параметры всегда **только для чтения** - это `val`, присвоить им нельзя:
```kotlin
fun bad(x: Int) {
// x = 5 // ошибка компиляции
val y = x + 1
println(y)
}
```
Тип параметра обязателен - вывода типов здесь нет.
## Значения по умолчанию
```kotlin
fun connect(host: String, port: Int = 8080, secure: Boolean = false) {
println("$host:$port secure=$secure")
}
fun main() {
connect("localhost") // localhost:8080 secure=false
connect("localhost", 443) // localhost:443 secure=false
connect("localhost", 443, true) // localhost:443 secure=true
}
```
Это заменяет перегрузки: вместо трёх версий метода - одна функция. Значением по умолчанию может быть любое выражение, включая вызов функции и другие параметры:
```kotlin
fun repeat2(text: String, times: Int = text.length) = text.repeat(times)
fun main() {
println(repeat2("ab")) // abab — times = 2
println(repeat2("ab", 3)) // ababab
}
```
Выражение вычисляется при каждом вызове, в котором аргумент опущен.
## Именованные аргументы
Аргумент можно передать по имени параметра:
```kotlin
fun connect(host: String, port: Int = 80, secure: Boolean = false, timeout: Int = 30) {
println("$host:$port s=$secure t=$timeout")
}
fun main() {
connect("localhost", secure = true) // пропустили port
connect(host = "localhost", timeout = 60)
connect(port = 443, host = "localhost") // порядок не важен
}
```
Две причины использовать:
1. **Пропустить** часть параметров со значениями по умолчанию
2. **Читаемость** на вызовах с непонятными литералами
```kotlin
// что здесь true?
// createUser("Иван", true, false, true)
// так понятно:
// createUser("Иван", admin = true, banned = false, verified = true)
```
Правило: позиционные аргументы идут до именованных. После первого именованного все следующие тоже должны быть именованными - кроме `vararg` со spread.
## vararg
Переменное число аргументов:
```kotlin
fun sum(vararg nums: Int): Int {
var total = 0
for (n in nums) total += n // nums имеет тип IntArray
return total
}
fun main() {
println(sum(1, 2, 3)) // 6
println(sum()) // 0
val arr = intArrayOf(1, 2, 3)
println(sum(*arr)) // 6 — spread-оператор обязателен
}
```
Правила:
- `vararg` может быть **только один** в функции
- он не обязан быть последним, но тогда остальные передаются по имени
- внутри функции это массив (`Array<T>` или специализированный)
- для передачи готового массива нужен `*` (spread)
```kotlin
fun tag(vararg classes: String, id: String) = "$id: ${classes.joinToString()}"
fun main() {
println(tag("a", "b", id = "x")) // x: a, b
}
```
## Локальные функции
Функция внутри функции - видит переменные внешней:
```kotlin
fun process(items: List<Int>): List<String> {
val prefix = ">> " // захватывается
fun format(x: Int): String = "$prefix$x" // локальная функция
return items.map { format(it) }
}
fun main() {
println(process(listOf(1, 2))) // [>> 1, >> 2]
}
```
Нужны, когда логика используется только внутри одной функции и не стоит того, чтобы выносить её в класс или в приватную функцию файла.
## Перегрузка
Функции с одним именем и разными параметрами:
```kotlin
fun show(x: Int) = "число $x"
fun show(x: String) = "строка $x"
fun show(x: Int, y: Int) = "пара $x,$y"
fun main() {
println(show(1)) // число 1
println(show("a")) // строка a
println(show(1, 2)) // пара 1,2
}
```
Сигнатуру образуют имя и типы параметров. Тип результата в неё **не входит** - две функции, отличающиеся только им, не скомпилируются.
Чаще всего перегрузка не нужна: её заменяют значения по умолчанию.
## Передача аргументов
Аргументы передаются **по значению**, как в Java:
- примитив - копируется значение
- объект - копируется ссылка
Значит, переприсвоить параметр внутри функции нельзя (он `val`), но изменяемый объект изменить можно:
```kotlin
fun addItem(list: MutableList<Int>) {
list.add(99) // объект изменён — виден снаружи
}
fun main() {
val items = mutableListOf(1)
addItem(items)
println(items) // [1, 99]
}
```
## Однострочные функции-расширения
Функции можно добавлять к существующим типам:
```kotlin
fun String.shout() = uppercase() + "!"
fun main() {
println("привет".shout()) // ПРИВЕТ!
}
```
Подробнее: [Функции-расширения](../../Функциональный%20Kotlin/Функции-расширения.md)
## Функции как значения
Функция может быть параметром и результатом:
```kotlin
fun apply2(x: Int, f: (Int) -> Int) = f(x)
fun main() {
println(apply2(3) { it * 2 }) // 6
println(apply2(3, ::square)) // 9 — ссылка на функцию
}
fun square(x: Int) = x * x
```
Подробнее: [Лямбды и функциональные типы](Лямбды%20и%20функциональные%20типы.md), [Функции высшего порядка](../../Функциональный%20Kotlin/Функции%20высшего%20порядка.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | метод только внутри класса | функция на верхнем уровне |
> | `int f(int x) { return x; }` | `fun f(x: Int) = x` |
> | `void` | `Unit` (можно опустить) |
> | нет значений по умолчанию | `fun f(x: Int = 0)` |
> | нет именованных аргументов | `f(x = 1)` |
> | `int... nums` | `vararg nums: Int` |
> | `f(arr)` для varargs | `f(*arr)` |
> | параметр можно переприсвоить | параметр - `val` |
> | перегрузки для опциональных параметров | одна функция со значениями по умолчанию |
>
> Значения по умолчанию + именованные аргументы убирают главный источник шума в Java API - «телескопические» перегрузки:
> ```java
> void connect(String host) { connect(host, 8080); }
> void connect(String host, int port) { connect(host, port, false); }
> void connect(String host, int port, boolean secure) { ... }
> ```
> В Kotlin это одна строка. Для вызова из Java есть `@JvmOverloads`, который генерирует такие перегрузки автоматически - см. [Kotlin из Java](../../Взаимодействие%20с%20Java/Kotlin%20из%20Java.md).
>
> Сравнить: [Функции в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Функции.md)
> [!warning] Подводные камни
> **Изменяемое значение по умолчанию вычисляется на каждом вызове** - в отличие от Python, где оно создаётся один раз. Это как раз правильное поведение:
> ```kotlin
> fun add(item: Int, list: MutableList<Int> = mutableListOf()): List<Int> {
> list.add(item)
> return list
> }
>
> fun main() {
> println(add(1)) // [1]
> println(add(2)) // [2] — новый список, а не [1, 2]
> }
> ```
>
> **Значения по умолчанию не видны из Java.** Без `@JvmOverloads` Java-код обязан передавать все аргументы.
>
> **Переопределённый метод не может изменить значения по умолчанию:**
> ```kotlin
> open class A { open fun f(x: Int = 1) = x }
> class B : A() {
> override fun f(x: Int) = x * 2 // значение по умолчанию наследуется,
> } // переобъявить его нельзя
> ```
>
> **`vararg` без spread принимает массив как один элемент:**
> ```kotlin
> fun show(vararg items: Any) = items.size
>
> fun main() {
> val arr = arrayOf(1, 2, 3)
> println(show(arr)) // 1 — массив целиком как один аргумент
> println(show(*arr)) // 3 — распакован
> }
> ```
>
> **Перегрузка + значения по умолчанию = неоднозначность.** Компилятор выберет наиболее специфичную перегрузку, а не ту, что со значениями по умолчанию:
> ```kotlin
> fun f(x: Int, y: Int = 0) = "два"
> fun f(x: Int) = "один"
>
> fun main() {
> println(f(1)) // "один" — перегрузка без default побеждает
> }
> ```
> Такого сочетания лучше избегать.
Дополнительно: [Лямбды и функциональные типы](Лямбды%20и%20функциональные%20типы.md)
Далее: [Лямбды и функциональные типы](Лямбды%20и%20функциональные%20типы.md)