12 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Функции | Объявление функций, значения по умолчанию, именованные аргументы, vararg и локальные функции |
|
Функция объявляется словом fun. В Kotlin она может жить на верхнем уровне файла, внутри класса или внутри другой функции.
fun greet(name: String): String {
return "Привет, $name"
}
fun main() {
println(greet("Kotlin")) // Привет, Kotlin
}
Порядок: fun → имя → параметры → : тип результата → тело.
Выражение вместо тела
Если функция - одно выражение, фигурные скобки и return убираются:
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 - отсутствие результата
fun log(message: String): Unit {
println(message)
}
fun log2(message: String) { // : Unit можно опустить
println(message)
}
Unit - настоящий тип с единственным значением, а не пустота. Подробнее: Any, Unit, Nothing
Параметры
Параметры всегда только для чтения - это val, присвоить им нельзя:
fun bad(x: Int) {
// x = 5 // ошибка компиляции
val y = x + 1
println(y)
}
Тип параметра обязателен - вывода типов здесь нет.
Значения по умолчанию
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
}
Это заменяет перегрузки: вместо трёх версий метода - одна функция. Значением по умолчанию может быть любое выражение, включая вызов функции и другие параметры:
fun repeat2(text: String, times: Int = text.length) = text.repeat(times)
fun main() {
println(repeat2("ab")) // abab — times = 2
println(repeat2("ab", 3)) // ababab
}
Выражение вычисляется при каждом вызове, в котором аргумент опущен.
Именованные аргументы
Аргумент можно передать по имени параметра:
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") // порядок не важен
}
Две причины использовать:
- Пропустить часть параметров со значениями по умолчанию
- Читаемость на вызовах с непонятными литералами
// что здесь true?
// createUser("Иван", true, false, true)
// так понятно:
// createUser("Иван", admin = true, banned = false, verified = true)
Правило: позиционные аргументы идут до именованных. После первого именованного все следующие тоже должны быть именованными - кроме vararg со spread.
vararg
Переменное число аргументов:
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)
fun tag(vararg classes: String, id: String) = "$id: ${classes.joinToString()}"
fun main() {
println(tag("a", "b", id = "x")) // x: a, b
}
Локальные функции
Функция внутри функции - видит переменные внешней:
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]
}
Нужны, когда логика используется только внутри одной функции и не стоит того, чтобы выносить её в класс или в приватную функцию файла.
Перегрузка
Функции с одним именем и разными параметрами:
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), но изменяемый объект изменить можно:
fun addItem(list: MutableList<Int>) {
list.add(99) // объект изменён — виден снаружи
}
fun main() {
val items = mutableListOf(1)
addItem(items)
println(items) // [1, 99]
}
Однострочные функции-расширения
Функции можно добавлять к существующим типам:
fun String.shout() = uppercase() + "!"
fun main() {
println("привет".shout()) // ПРИВЕТ!
}
Подробнее: Функции-расширения
Функции как значения
Функция может быть параметром и результатом:
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
Подробнее: Лямбды и функциональные типы, Функции высшего порядка
[!info] Отличия от Java
Java Kotlin метод только внутри класса функция на верхнем уровне int f(int x) { return x; }fun f(x: Int) = xvoidUnit(можно опустить)нет значений по умолчанию fun f(x: Int = 0)нет именованных аргументов f(x = 1)int... numsvararg nums: Intf(arr)для varargsf(*arr)параметр можно переприсвоить параметр - valперегрузки для опциональных параметров одна функция со значениями по умолчанию Значения по умолчанию + именованные аргументы убирают главный источник шума в Java API - «телескопические» перегрузки:
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.Сравнить: Функции в Java
[!warning] Подводные камни Изменяемое значение по умолчанию вычисляется на каждом вызове - в отличие от Python, где оно создаётся один раз. Это как раз правильное поведение:
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. Без
@JvmOverloadsJava-код обязан передавать все аргументы.Переопределённый метод не может изменить значения по умолчанию:
open class A { open fun f(x: Int = 1) = x } class B : A() { override fun f(x: Int) = x * 2 // значение по умолчанию наследуется, } // переобъявить его нельзя
varargбез spread принимает массив как один элемент:fun show(vararg items: Any) = items.size fun main() { val arr = arrayOf(1, 2, 3) println(show(arr)) // 1 — массив целиком как один аргумент println(show(*arr)) // 3 — распакован }Перегрузка + значения по умолчанию = неоднозначность. Компилятор выберет наиболее специфичную перегрузку, а не ту, что со значениями по умолчанию:
fun f(x: Int, y: Int = 0) = "два" fun f(x: Int) = "один" fun main() { println(f(1)) // "один" — перегрузка без default побеждает }Такого сочетания лучше избегать.
Дополнительно: Лямбды и функциональные типы Далее: Лямбды и функциональные типы