Files
obsidian/Kotlin/Синтаксис и типы/Работа с данными/Функции.md
T

281 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)