Функции
Чтобы объявить функцию в Kotlin:
- используйте ключевое слово
fun; - укажите параметры в круглых скобках
(); - добавьте тип возвращаемого значения, если он нужен.
Например:
// 'double' - имя функции
// 'x' - параметр типа Int
// ожидаемый тип возвращаемого значения тоже Int
fun double(x: Int): Int {
return 2 * x
}
fun main() {
println(double(5))
// 10
}
Использование функций
При вызове функции используется традиционный подход:
val result = double(2)
Чтобы вызвать функцию-член или функцию-расширение, используйте
точку .:
// Создаёт экземпляр класса Stream и вызывает read()
Stream().read()
Параметры
Параметры функции объявляются с помощью Pascal-нотации: имя: Тип. Параметры разделяются запятыми, и тип каждого
параметра должен быть указан явно:
fun powerOf(number: Int, exponent: Int): Int { /*...*/ }
Внутри тела функции полученные аргументы доступны только для чтения (они неявно объявлены как val):
fun powerOf(number: Int, exponent: Int): Int {
number = 2 // Ошибка: 'val' нельзя переназначить
}
Вы можете использовать завершающую запятую при объявлении параметров функции:
fun powerOf(
number: Int,
exponent: Int, // завершающая запятая
) { /*...*/ }
Завершающие запятые помогают при рефакторинге и сопровождении кода: вы можете перемещать параметры внутри объявления и не думать о том, какой из них окажется последним.
Функции Kotlin могут принимать другие функции в качестве параметров, а также передаваться как аргументы. Подробнее см. Функции высшего порядка и лямбды.
Параметры со значениями по умолчанию
Параметр функции можно сделать необязательным, указав для него значение по умолчанию. Kotlin использует значение по умолчанию, когда вы вызываете функцию без аргумента, соответствующего этому параметру. Параметры со значениями по умолчанию также называют опциональными параметрами.
Опциональные параметры уменьшают потребность в перегрузках: вам не нужно объявлять несколько версий одной функции только для того, чтобы разрешить пропуск параметра с разумным значением по умолчанию.
Чтобы задать значение по умолчанию, добавьте = к объявлению параметра:
fun read(
b: ByteArray,
// Значение 'off' по умолчанию равно 0
off: Int = 0,
// Значение 'len' по умолчанию вычисляется
// как размер массива 'b'
len: Int = b.size,
) { /*...*/ }
Когда параметр со значением по умолчанию объявлен перед параметром без значения по умолчанию, использовать это значение можно только через именованный аргумент:
fun greeting(
userId: Int = 0,
message: String,
) { /*...*/ }
fun main() {
// Используется значение 0 по умолчанию для 'userId'
greeting(message = "Hello!")
// Ошибка: для параметра 'userId' не передано значение
greeting("Hello!")
}
Завершающие лямбды являются исключением из этого правила, поскольку последний параметр должен соответствовать переданной функции:
fun main() {
fun greeting(
userId: Int = 0,
message: () -> Unit,
) {
println(userId)
message()
}
// Используется значение по умолчанию для 'userId'
greeting() { println("Hello!") }
// 0
// Hello!
}
Переопределённые методы всегда используют значения параметров по умолчанию из базового метода. При переопределении метода, у которого есть значения параметров по умолчанию, эти значения должны быть опущены из сигнатуры:
open class Shape {
open fun draw(width: Int = 10, height: Int = 5) { /*...*/ }
}
class Rectangle : Shape() {
// Здесь нельзя указывать значения по умолчанию,
// но эта функция всё равно по умолчанию использует
// 10 для 'width' и 5 для 'height'.
override fun draw(width: Int, height: Int) { /*...*/ }
}
Неконстантные выражения как значения по умолчанию
Параметру можно назначить значение по умолчанию, которое не является константой. Например, значением по умолчанию может
быть результат вызова функции или вычисления, использующего значения других аргументов, как параметр len в этом
примере:
fun read(
b: ByteArray,
off: Int = 0,
len: Int = b.size,
) { /*...*/ }
Параметры, которые ссылаются на значения других параметров, должны быть объявлены позже них. В этом примере len должен
быть объявлен после b.
В общем случае значением параметра по умолчанию может быть любое выражение. Однако значения по умолчанию вычисляются
только тогда, когда функция вызывается без соответствующего параметра и нужно подставить значение по умолчанию. Например,
эта функция выводит строку только при вызове без параметра print:
fun main() {
fun read(
b: Int,
print: Unit? = println("Аргумент 'print' не передан")
) {
println(b)
}
// Выводит "Аргумент 'print' не передан", затем "1"
read(1)
// Выводит только "1"
read(1, null)
}
Если последний параметр в объявлении функции имеет функциональный тип, соответствующий лямбда-аргумент можно передать либо как именованный аргумент, либо за скобками:
fun main() {
fun log(
level: Int = 0,
code: Int = 1,
action: () -> Unit,
) {
println(level)
println(code)
action()
}
// Передаёт 1 для 'level' и использует значение 1 по умолчанию для 'code'
log(1) { println("Connection established") }
// Использует оба значения по умолчанию: 0 для 'level' и 1 для 'code'
log(action = { println("Connection established") })
// Эквивалентно предыдущему вызову, использует оба значения по умолчанию
log { println("Connection established") }
}
Именованные аргументы
При вызове функции вы можете явно указать имена одного или нескольких аргументов. Это может быть полезно, когда у вызова
функции много аргументов. В таких случаях сложно связать значение с конкретным аргументом, особенно если это null или
логическое значение.
При использовании именованных аргументов в вызове функции их можно перечислять в любом порядке.
Рассмотрим функцию reformat(), у которой есть четыре аргумента со значениями по умолчанию:
fun reformat(
str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ',
) { /*...*/ }
При вызове этой функции можно явно указать имена некоторых аргументов:
reformat(
"String!",
normalizeCase = false,
upperCaseFirstLetter = false,
divideByCamelHumps = true,
'_'
)
Вы можете пропустить все аргументы со значениями по умолчанию:
reformat("This is a long String!")
Вы также можете пропустить только некоторые аргументы со значениями по умолчанию, а не все сразу. Однако после первого пропущенного аргумента вы должны указывать имена всех последующих аргументов:
reformat(
"This is a short String!",
upperCaseFirstLetter = false,
wordSeparator = '_'
)
Вы можете передать переменное количество аргументов (vararg), указав имя
соответствующего аргумента. В этом примере передаётся массив:
fun mergeStrings(vararg strings: String) { /*...*/ }
mergeStrings(strings = arrayOf("a", "b", "c"))
При вызове Java-функций на JVM нельзя использовать синтаксис именованных аргументов, потому что байт-код Java не всегда сохраняет имена параметров функции.
Типы возвращаемых значений
Когда вы объявляете функцию с блочным телом (инструкциями в фигурных скобках {}), тип возвращаемого значения всегда
нужно указывать явно. Единственное исключение - функции, возвращающие Unit: в этом случае тип можно не указывать.
Kotlin не выводит типы возвращаемых значений для функций с блочным телом. Их поток управления может быть сложным, из-за чего тип возвращаемого значения становится неочевидным для читателя, а иногда даже для компилятора. Однако Kotlin может вывести тип для функций с одним выражением, если вы не указали его явно.
Функции с одним выражением
Когда тело функции состоит из одного выражения, фигурные скобки {} можно опустить, а тело указать после символа =:
fun double(x: Int): Int = x * 2
В большинстве случаев тип возвращаемого значения не нужно объявлять явно:
// Компилятор выводит, что функция возвращает Int
fun double(x: Int) = x * 2
Иногда компилятор может столкнуться с проблемами при выводе типа возвращаемого значения из одного выражения. В таких
случаях тип следует добавить явно. Например, функции, которые являются рекурсивными или взаимно рекурсивными (вызывают
друг друга), а также функции с нетипизированными выражениями вроде fun empty() = null, всегда требуют явного типа
возвращаемого значения.
Когда вы используете выведенный тип возвращаемого значения, проверяйте фактический результат: компилятор может вывести
тип, который окажется менее полезным для вас. В примере выше, если вы хотите, чтобы функция double() возвращала
Number, а не Int, это нужно указать явно.
Функции с возвращаемым типом Unit
Если у функции есть блочное тело (инструкции в фигурных скобках {}), и она не возвращает полезного значения, компилятор
считает, что её тип возвращаемого значения - Unit. Unit - это тип, у которого есть только одно значение, также
называемое Unit.
Указывать Unit как возвращаемый тип самой функции не нужно. Исключение - параметры функционального типа, например
() -> Unit. Возвращать Unit явно никогда не требуется.
Например, функцию printHello() можно объявить без возвращения Unit:
// В объявлении параметра функционального типа ('action')
// всё равно нужен явный возвращаемый тип
fun printHello(name: String?, action: () -> Unit) {
if (name != null)
println("Hello $name")
else
println("Hi there!")
action()
}
fun main() {
printHello("Kodee") {
println("This runs after the greeting.")
}
// Hello Kodee
// This runs after the greeting.
printHello(null) {
println("No name provided, but action still runs.")
}
// No name provided, but action still runs
}
Это эквивалентно следующему более подробному объявлению:
fun printHello(name: String?, action: () -> Unit): Unit {
if (name != null)
println("Hello $name")
else
println("Hi there!")
action()
return Unit
}
fun main() {
printHello("Kodee") {
println("This runs after the greeting.")
}
// Hello Kodee
// This runs after the greeting.
printHello(null) {
println("No name provided, but action still runs.")
}
// No name provided, but action still runs
}
В теле-выражении можно использовать оператор return, если тип возвращаемого значения функции указан явно:
fun getDisplayNameOrDefault(userId: String?): String =
getDisplayName(userId ?: return "default")
Нефиксированное число аргументов (varargs)
Чтобы передать в функцию переменное количество аргументов, один из её параметров (обычно последний) можно пометить
модификатором vararg. Внутри функции vararg-параметр типа T можно использовать как массив элементов T:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts - это массив
result.add(t)
return result
}
После этого в функцию можно передать переменное количество аргументов:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts - это массив
result.add(t)
return result
}
fun main() {
val list = asList(1, 2, 3)
println(list)
// [1, 2, 3]
}
Только один параметр может быть помечен как vararg. Если vararg-параметр объявлен не последним в списке параметров,
значения для следующих параметров нужно передавать с помощью именованных аргументов. Если параметр имеет функциональный
тип, его значение также можно передать, поместив лямбду за скобками.
При вызове vararg-функции можно передавать аргументы по отдельности, как в примере asList(1, 2, 3). Если у вас уже
есть массив и вы хотите передать его содержимое в функцию как vararg-параметр или его часть, используйте оператор
spread, добавив * перед именем массива:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts)
result.add(t)
return result
}
fun main() {
val a = arrayOf(1, 2, 3)
// Функция получает массив [-1, 0, 1, 2, 3, 4]
val list = asList(-1, 0, *a, 4)
println(list)
// [-1, 0, 1, 2, 3, 4]
}
Если вы хотите передать массив примитивного типа как vararg, его нужно
преобразовать в обычный (типизированный) массив с помощью функции
toTypedArray():
// 'a' - это IntArray, массив примитивного типа
val a = intArrayOf(1, 2, 3)
val list = asList(-1, 0, *a.toTypedArray(), 4)
Инфиксная запись
С помощью ключевого слова infix можно объявлять функции, которые вызываются без круглых скобок и точки. Это может
сделать простые вызовы функций в коде более читаемыми.
infix fun Int.shl(x: Int): Int { /*...*/ }
// Вызов функции с помощью обычной записи
1.shl(2)
// Вызов функции с помощью инфиксной записи
1 shl 2
Инфиксные функции должны соответствовать следующим требованиям:
- они должны быть функциями-членами класса или функциями-расширениями;
- они должны иметь только один параметр;
- параметр не должен принимать переменное количество аргументов (
vararg) и не должен иметь значения по умолчанию.
Вызовы инфиксных функций имеют более низкий приоритет, чем арифметические операторы, приведение типов и оператор
rangeTo. Следующие выражения эквивалентны:
1 shl 2 + 3эквивалентно1 shl (2 + 3),0 until n * 2эквивалентно0 until (n * 2),xs union ys as Set<*>эквивалентноxs union (ys as Set<*>).С другой стороны, приоритет вызова инфиксной функции выше, чем у логических операторов
&&и||,is- иin-проверок и некоторых других операторов. Эти выражения также эквивалентны:
a && b xor cэквивалентноa && (b xor c),a xor b in cэквивалентно(a xor b) in c.
Обратите внимание, что инфиксные функции всегда требуют указания и получателя, и параметра. Когда вы вызываете метод на
текущем получателе с помощью инфиксной записи, явно используйте this. Это обеспечивает однозначный синтаксический
разбор.
class MyStringCollection {
val items = mutableListOf<String>()
infix fun add(s: String) {
println("Adding: $s")
items += s
}
fun build() {
add("first") // Верно: обычный вызов функции
this add "second" // Верно: инфиксный вызов с явным получателем
// add "third" // Ошибка компиляции: нужен явный получатель
}
fun printAll() = println("Items = $items")
}
fun main() {
val myStrings = MyStringCollection()
// Добавляет "first" и "second" в список
myStrings.build()
myStrings.printAll()
// Adding: first
// Adding: second
// Items = [first, second]
}
Область видимости функций
Функции Kotlin можно объявлять на верхнем уровне файла: вам не нужно создавать класс только для того, чтобы поместить в него функцию. Функции также можно объявлять локально, как функции-члены или функции-расширения.
Локальные функции
Kotlin поддерживает локальные функции, то есть функции, объявленные внутри других функций. Например, следующий код
реализует алгоритм поиска в глубину для заданного графа. Локальная функция dfs() внутри внешней функции dfs()
скрывает реализацию и обрабатывает рекурсивные вызовы:
class Person(val name: String) {
val friends = mutableListOf<Person>()
}
class SocialGraph(val people: List<Person>)
fun dfs(graph: SocialGraph) {
fun dfs(current: Person, visited: MutableSet<Person>) {
if (!visited.add(current)) return
println("Visited ${current.name}")
for (friend in current.friends)
dfs(friend, visited)
}
dfs(graph.people[0], HashSet())
}
fun main() {
val alice = Person("Alice")
val bob = Person("Bob")
val charlie = Person("Charlie")
alice.friends += bob
bob.friends += charlie
charlie.friends += alice
val network = SocialGraph(listOf(alice, bob, charlie))
dfs(network)
}
Локальная функция может получать доступ к локальным переменным внешних функций (замыкание). В примере выше параметр
функции visited может быть локальной переменной:
class Person(val name: String) {
val friends = mutableListOf<Person>()
}
class SocialGraph(val people: List<Person>)
fun dfs(graph: SocialGraph) {
val visited = HashSet<Person>()
fun dfs(current: Person) {
if (!visited.add(current)) return
println("Visited ${current.name}")
for (friend in current.friends)
dfs(friend)
}
dfs(graph.people[0])
}
fun main() {
val alice = Person("Alice")
val bob = Person("Bob")
val charlie = Person("Charlie")
alice.friends += bob
bob.friends += charlie
charlie.friends += alice
val network = SocialGraph(listOf(alice, bob, charlie))
dfs(network)
}
Функции-члены
Функция-член - это функция, объявленная внутри класса или объекта:
class Sample {
fun foo() { print("Foo") }
}
Чтобы вызвать функцию-член, напишите имя экземпляра или объекта, затем добавьте . и имя функции:
// Создаёт экземпляр класса Stream и вызывает read()
Stream().read()
Для более подробной информации о классах и переопределении членов см. Классы и Наследование.
Функции-обобщения
Обобщённые параметры функции можно указать с помощью угловых скобок <> перед именем функции:
fun <T> singletonList(item: T): List<T> { /*...*/ }
Для более подробной информации об обобщённых функциях см. Обобщения.
Функции с хвостовой рекурсией
Kotlin поддерживает стиль функционального программирования, известный как хвостовая рекурсия.
Для некоторых алгоритмов, которые обычно реализуют с помощью циклов, можно использовать рекурсивную функцию без риска
переполнения стека. Когда функция помечена модификатором tailrec и соответствует формальным требованиям, компилятор
оптимизирует рекурсию, оставляя вместо неё быструю и эффективную версию на основе цикла:
import kotlin.math.cos
import kotlin.math.abs
// Произвольная "достаточно хорошая" точность
val eps = 1E-10
tailrec fun findFixPoint(x: Double = 1.0): Double =
if (abs(x - cos(x)) < eps) x else findFixPoint(cos(x))
Этот код вычисляет неподвижную точку косинуса (математическую константу). Функция многократно вызывает cos(), начиная с
1.0, пока результат не перестанет изменяться, и для указанной точности eps получает значение 0.7390851332151611.
Код эквивалентен следующей более традиционной форме:
import kotlin.math.cos
import kotlin.math.abs
// Произвольная "достаточно хорошая" точность
val eps = 1E-10
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = cos(x)
if (abs(x - y) < eps) return x
x = cos(x)
}
}
Модификатор tailrec можно применять к функции только тогда, когда она вызывает саму себя в качестве последней
операции. Хвостовую рекурсию нельзя использовать, если после рекурсивного вызова есть другой код, внутри блоков
try/catch/finally или когда функция является open.
См. также: