Spec-Zone.ru › Kotlin 2

Функции

Чтобы объявить функцию в Kotlin:

  • Используйте ключевое слово fun.

  • Укажите параметры в круглых скобках ().

  • При необходимости укажите тип возвращаемого значения.

Например:

//sampleStart
// 'double' is the name of the function
// 'x' is a parameter of Int type
// The expected return value is of Int type too
fun double(x: Int): Int {
    return 2 * x
}
//sampleEnd

fun main() {
    println(double(5))
    // 10
}

Использование функций

Функции вызываются стандартным способом:

val result = double(2)

Чтобы вызвать член класса или функцию-расширение, используйте точку .:

// Creates an instance of the Stream class and calls read()
Stream().read()

Параметры

Объявляйте параметры функций, используя нотацию Паскаля: name: Type. Параметры необходимо разделять запятыми, а тип каждого параметра указывать явно:

fun powerOf(number: Int, exponent: Int): Int { /*...*/ }

При передаче объекта в функцию компилятор передаёт копию ссылки на этот объект. Скопированная ссылка указывает на тот же объект, поэтому функция может изменять его изменяемое состояние.

Параметры функций доступны только для чтения внутри тела функции (неявно объявлены как val), поэтому переназначить им значения нельзя:

class Counter(var value: Int)

fun reset(counter: Counter) {
    counter.value = 0    // Allowed: modifies the object
    counter = Counter(0) // Error: 'val' cannot be reassigned
}

При объявлении параметров функции можно использовать завершающую запятую:

fun powerOf(
    number: Int,
    exponent: Int, // trailing comma
) { /*...*/ }

Завершающие запятые упрощают рефакторинг и обслуживание кода: параметры можно перемещать в объявлении, не беспокоясь о том, какой из них окажется последним.

Функции Kotlin могут принимать другие функции в качестве параметров и передаваться в качестве аргументов. Дополнительную информацию см. в разделе Функции высшего порядка и лямбды.

Параметры со значениями по умолчанию

Чтобы сделать параметр функции необязательным, укажите для него значение по умолчанию. Kotlin использует значение по умолчанию, если вы вызываете функцию без аргумента, соответствующего этому параметру. Параметры со значениями по умолчанию также называются необязательными параметрами.

Необязательные параметры позволяют реже использовать перегрузки: не нужно объявлять разные версии функции только для того, чтобы можно было пропустить параметр с подходящим значением по умолчанию.

Чтобы задать значение по умолчанию, добавьте = к объявлению параметра:

fun read(
    b: ByteArray,
    // The default value of 'off' is 0
    off: Int = 0,
    // The default value of 'len' is calculated
    // as the size of the 'b' array
    len: Int = b.size,
) { /*...*/ }

Если параметр со значением по умолчанию объявлен перед параметром без значения по умолчанию, использовать значение по умолчанию можно только указав имя аргумента:

fun greeting(
    userId: Int = 0,
    message: String,
) { /*...*/ }

fun main() {
    // Uses 0 as the default value for 'userId'
    greeting(message = "Hello!")
    
    // Error: No value passed for parameter 'message'
    greeting("Hello!")
}

Завершающие лямбды — исключение из этого правила, поскольку последний параметр должен соответствовать переданной функции:

fun main () {
//sampleStart    
fun greeting(
    userId: Int = 0,
    message: () -> Unit,
)
{ println(userId)
  message() }
    
// Uses the default value for 'userId'
greeting() { println ("Hello!") }
// 0
// Hello!
//sampleEnd
}

Переопределённые методы всегда используют значения параметров по умолчанию базового метода. При переопределении метода, параметры которого имеют значения по умолчанию, необходимо опустить эти значения в сигнатуре:

open class Shape {
    open fun draw(width: Int = 10, height: Int = 5) { /*...*/ }
}

class Rectangle : Shape() {
    // It's not allowed to specify default values here
    // but this function also uses 10 for 'width' and 5 for 'height'
    // by default.
    override fun draw(width: Int, height: Int) { /*...*/ }
}

Неконстантные выражения в качестве значений по умолчанию

Параметру можно присвоить неконстантное значение по умолчанию. Например, значением по умолчанию может быть результат вызова функции или вычисления с использованием значений других аргументов, как для параметра len в этом примере:

fun read(
    b: ByteArray,
    off: Int = 0,
    len: Int = b.size,
) { /*...*/ }

Параметры, ссылающиеся на значения других параметров, необходимо объявлять позже. В этом примере len необходимо объявить после b.

В общем случае в качестве значения параметра по умолчанию можно указать любое выражение. Однако значения по умолчанию вычисляются только при вызове функции без соответствующего параметра, если для него нужно присвоить значение по умолчанию. Например, эта функция выводит строку только при вызове без параметра print:

fun main() {
//sampleStart
    fun read(
        b: Int,
        print: Unit? = println("No argument passed for 'print'")
    ) { println(b) }
    
    // Prints "No argument passed for 'print'", then "1"
    read(1)
    // Prints only "1"
    read(1, null)
    //sampleEnd
}

Если последний параметр в объявлении функции имеет функциональный тип, соответствующий аргумент-лямбду можно передать как именованный аргумент или за пределами круглых скобок:

fun main() {
    //sampleStart
    fun log(
        level: Int = 0,
        code:  Int = 1,
        action: () -> Unit,
    ) { println (level)
        println (code)
        action() }
    
    // Passes 1 for 'level' and uses the default value 1 for 'code'
    log(1) { println("Connection established") }
    
    // Uses both default values, 0 for 'level' and 1 for 'code'
    log(action = { println("Connection established") })
    
    // Equivalent to the previous call, uses both default values
    log { println("Connection established") }
    //sampleEnd
}

Именованные аргументы

При вызове функции можно указать имена одного или нескольких её аргументов. Это может быть полезно, если у функции много аргументов. В таких случаях сложно соотнести значение с аргументом, особенно если это null или логическое значение.

При использовании именованных аргументов в вызове функции их можно перечислять в любом порядке.

Рассмотрим функцию reformat(), у которой есть 4 аргумента со значениями по умолчанию:

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 может вывести тип возвращаемого значения функций с единственным выражением, если не указывать его явно.

Функции Kotlin возвращают одно значение, но оно может содержать несколько элементов данных. О способах представления таких значений см. в разделе Возврат нескольких значений.

Возврат нескольких значений

Если нужно вернуть несколько связанных значений с различным смыслом, объявите класс данных, даже если он используется только в одной функции:

data class OrderSummary(
    val subtotal: Double,
    val tax: Double,
)

fun calculateOrderSummary(prices: List<Double>): OrderSummary {
    val subtotal = prices.sum()
    val tax = subtotal * 0.2
    return OrderSummary(subtotal, tax)
}

fun main() {
    val summary = calculateOrderSummary(listOf(12.50, 8.00, 4.50))

    println(summary.subtotal)
    // 25.0
    println(summary.tax)
    // 5.0
}

Класс данных хорошо подходит, когда нужно вернуть несколько значений с различным смыслом. Если возвращаемые значения имеют один тип и их нужно обрабатывать как группу, рассмотрите возможность возврата коллекции:

data class Person(val name: String)

val friendGroups = listOf(
    listOf(Person("Alice"), Person("Bob")),
    listOf(Person("Charlie"), Person("Diana"), Person("Eve")),
    listOf(Person("Frank"))
)

fun findLargestGroupOfFriends(): List<Person> {
    return friendGroups.maxByOrNull { it.size } ?: emptyList()
}

fun main() {
    val largestGroup = findLargestGroupOfFriends()

    println(largestGroup.map { it.name })
    // [Charlie, Diana, Eve]
}

Если нужно вернуть фиксированное число значений, можно также использовать классы данных Pair или Triple из стандартной библиотеки. Однако их свойства имеют общие имена, например first, second и third, из-за чего результат может быть сложно понять.

Например, хотя функция calculateOrderTotals() возвращает Pair, непонятно, что представляет собой каждый Double:

fun calculateOrderTotals(prices: List<Double>): Pair<Double, Double> {
    val subtotal = prices.sum()
    val tax = subtotal * 0.2
    return Pair(subtotal, tax)
}

fun main() {
    val totals = calculateOrderTotals(listOf(12.50, 8.00, 4.50))

    // What does 'first' mean?
    println(totals.first)
    // 25.0
  
    // What does 'second' mean?
    println(totals.second)
    // 5.0
}

Для результатов с различным смыслом предпочтительнее использовать класс данных с описательными именами свойств, как показано в примере класса данных OrderSummary.

Функции с единственным выражением

Если тело функции состоит из одного выражения, можно опустить фигурные скобки и указать тело после символа =:

fun double(x: Int): Int = x * 2

В большинстве случаев тип возвращаемого значения не нужно объявлять явно:

// Compiler infers that the function returns Int
fun double(x: Int) = x * 2

Иногда при выводе типов возвращаемых значений из единственных выражений у компилятора могут возникнуть затруднения. В таких случаях следует явно указать тип возвращаемого значения. Например, тип всегда необходимо указывать для рекурсивных или взаимно рекурсивных функций (вызывающих друг друга), а также для функций с выражениями без типа, например fun empty() = null.

Если используется выведенный тип возвращаемого значения, обязательно проверьте фактический результат: компилятор может вывести тип, который окажется для вас менее подходящим. В приведённом выше примере, если нужно, чтобы функция double() возвращала Number вместо Int, это необходимо указать явно.

Если в теле-выражении используется оператор return, необходимо явно указать тип возвращаемого значения:

fun getDisplayNameOrDefault(userId: String?): String =
    getDisplayName(userId ?: return "default")

Функции, возвращающие Unit

Если функция имеет блочное тело (инструкции внутри фигурных скобок {}) и не возвращает полезного значения, компилятор считает, что её тип возвращаемого значения — Unit. Unit — это тип, имеющий только одно значение, которое также называется Unit.

Указывать Unit в качестве типа возвращаемого значения не нужно, за исключением параметров функционального типа. Возвращать Unit явно также не нужно.

Например, можно объявить функцию printHello() без возврата Unit:

// The declaration of the functional type parameter ('action') still 
// needs an explicit return type
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
}

Это равнозначно следующему многословному объявлению:

//sampleStart
fun printHello(name: String?, action: () -> Unit): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")

    action()
    return Unit
}
//sampleEnd
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
}

Переменное число аргументов (varargs)

Чтобы передать функции переменное число аргументов, можно пометить один из её параметров (обычно последний) модификатором vararg. Внутри функции параметр типа T с модификатором vararg можно использовать как массив значений типа T:

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

Затем функции можно передать переменное число аргументов:

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

fun main() {
    //sampleStart
    val list = asList(1, 2, 3)
    println(list)
    // [1, 2, 3]
    //sampleEnd
}

Модификатором 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() {
    //sampleStart
    val a = arrayOf(1, 2, 3)

    // The function receives the array [-1, 0, 1, 2, 3, 4]
    list = asList(-1, 0, *a, 4)

    println(list)
    // [-1, 0, 1, 2, 3, 4]
    //sampleEnd
}

Чтобы передать массив примитивного типа в качестве vararg, сначала преобразуйте его в обычный (типизированный) массив с помощью функции .toTypedArray():

// 'a' is an IntArray, which is a primitive type array
val a = intArrayOf(1, 2, 3)
val list = asList(-1, 0, *a.toTypedArray(), 4)

Инфиксная нотация

С помощью ключевого слова infix можно объявлять функции, вызываемые без круглых скобок и точки. Это может упростить чтение кода с простыми вызовами функций.

infix fun Int.shl(x: Int): Int { /*...*/ }

// Calls the function using the general notation 
1.shl(2)

// Calls the function using the infix notation
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")      // Correct: ordinary function call
        this add "second" // Correct: infix call with an explicit receiver
        // add "third"    // Compiler error: needs an explicit receiver
    }

    fun printAll() = println("Items = $items")
}

fun main() {
    val myStrings = MyStringCollection()
    // Adds "first" and "second" to the list
    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>)
//sampleStart
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())
}
//sampleEnd
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>)
//sampleStart
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])
}
//sampleEnd
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") }
}

Чтобы вызвать функцию-член, укажите имя экземпляра или объекта, затем добавьте . и укажите имя функции:

// Creates an instance of the Stream class and calls read()
Stream().read()

Дополнительную информацию о классах и переопределении членов см. в разделах Классы и Наследование.

Обобщённые функции

Чтобы задать обобщённые параметры функции, используйте угловые скобки <> перед именем функции:

fun <T> singletonList(item: T): List<T> { /*...*/ }

Дополнительную информацию об обобщённых функциях см. в разделе Обобщения.

Хвостовая рекурсия

Kotlin поддерживает стиль функционального программирования, известный как хвостовая рекурсия. Для некоторых алгоритмов, которые обычно реализуются с помощью циклов, можно использовать рекурсивную функцию без риска переполнения стека. Если функция помечена модификатором tailrec и соответствует необходимым формальным условиям, компилятор устраняет рекурсию и заменяет её быстрым и эффективным циклом:

import kotlin.math.cos
import kotlin.math.abs

// An arbitrary "good enough" precision
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

// An arbitrary "good enough" precision
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 или если функция является открытой.

См. также:

  • Встраиваемые функции

  • Функции-расширения

  • Функции высшего порядка и лямбды

31 августа 2026 г.
Обработка исключений и ошибокФункции высшего порядка и лямбды

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/functions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API