Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user