Spec-Zone.ru › Kotlin 1.8

Функции

Функции Kotlin объявляются с использованием ключевого слова fun:

fun double(x: Int): Int {
    return 2 * x
}

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

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

val result = double(2)

Вызов функций-членов осуществляется с использованием нотации точки:

Stream().read() // create instance of class Stream and call read()

Параметры

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

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

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

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

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

Параметры функций могут иметь значения по умолчанию, которые используются, когда соответствующий аргумент опущен. Это сокращает количество перегрузок:

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

Значение по умолчанию задается путем добавления = к типу.

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

open class A {
    open fun foo(i: Int = 10) { /*...*/ }
}

class B : A() {
    override fun foo(i: Int) { /*...*/ }  // No default value is allowed.
}

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

fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }

foo(baz = 1) // The default value bar = 0 is used

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

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*...*/ }

foo(1) { println("hello") }     // Uses the default value baz = 1
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1
foo { println("hello") }        // Uses both default values bar = 0 and baz = 1

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

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

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

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

fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*...*/ }

При вызове этой функции необязательно указывать имена всех аргументов:

reformat(
    "String!",
    false,
    upperCaseFirstLetter = false,
    divideByCamelHumps = true,
    '_'
)

Можно пропустить все аргументы со значениями по умолчанию:

reformat("This is a long String!")

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

reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')

Можно передать переменное число аргументов (vararg) с именами, используя оператор spread:

fun foo(vararg strings: String) { /*...*/ }

foo(strings = *arrayOf("a", "b", "c"))

При вызове функций Java на JVM нельзя использовать синтаксис именованных аргументов, так как байт-код Java не всегда сохраняет имена параметров функций.

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

Если функция не возвращает полезного значения, ее тип возвращаемого значения — Unit. Unit — тип, содержащий только одно значение — Unit. Это значение не нужно возвращать явно:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

Декларация типа возвращаемого значения Unit также необязательна. Приведенный выше код эквивалентен:

fun printHello(name: String?) { ... }

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

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

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

Явное объявление типа возвращаемого значения необязательно, если его можно вывести из компилятора:

fun double(x: Int) = x * 2

Явные типы возвращаемого значения

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

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

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

Можно пометить параметр функции (обычно последний) модификатором vararg:

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
}

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

val list = asList(1, 2, 3)

Внутри функции параметр vararg типа T представлен как массив T, как в примере выше, где переменная ts имеет тип Array<out T>.

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

При вызове функции с vararg можно передавать аргументы по отдельности, например asList(1, 2, 3). Если у вас уже есть массив, и вы хотите передать его содержимое в функцию, используйте оператор распределения (предложите массив *):

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

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

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

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

Функции, помеченные ключевым словом infix , также могут вызываться с использованием инфиксной нотации (опуская точку и скобки для вызова). Инфиксные функции должны соответствовать следующим требованиям:

  • Они должны быть функциями-членами или расширяющими функциями.

  • Они должны иметь один параметр.

  • Параметр не должен принимать переменное число аргументов и не должен иметь значения по умолчанию.

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

// calling the function using the infix notation
1 shl 2

// is the same as
1.shl(2)

Вызовы инфиксных функций имеют более низкий приоритет, чем арифметические операторы, преобразования типов и оператор 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 {
    infix fun add(s: String) { /*...*/ }
    
    fun build() {
        this add "abc"   // Correct
        add("abc")       // Correct
        //add "abc"        // Incorrect: the receiver must be specified
    }
}

Область видимости функций

Функции Kotlin могут объявляться на верхнем уровне в файле, то есть вам не нужно создавать класс для хранения функции, что требуется в таких языках, как Java, C# и Scala (функции на верхнем уровне доступны начиная со Scala 3). В дополнение к функциям на верхнем уровне функции Kotlin также могут быть объявлены локально как функции-члены и расширяющие функции.

Локальные функции

Kotlin поддерживает локальные функции, которые являются функциями внутри других функций:

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

Локальная функция может обращаться к локальным переменным внешних функций (замыкание). В приведенном выше случае visited может быть локальной переменной:

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

Функции-члены

Функция-член — это функция, которая определена внутри класса или объекта:

class Sample {
    fun foo() { print("Foo") }
}

Функции-члены вызываются с помощью нотации точки:

Sample().foo() // creates instance of class Sample and calls foo

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

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

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

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

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

Функции хвостовой рекурсии

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

val eps = 1E-10 // "good enough", could be 10^-15

tailrec fun findFixPoint(x: Double = 1.0): Double =
    if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))

Этот код вычисляет fixpoint, которая является математической константой. Он просто многократно вызывает Math.cos, начиная с 1.0, пока результат больше не изменится, получая результат 0.7390851332151611 для заданной точности eps. Результирующий код эквивалентен этому более традиционному стилю:

val eps = 1E-10 // "good enough", could be 10^-15

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (Math.abs(x - y) < eps) return x
        x = Math.cos(x)
    }
}

Чтобы иметь право на применение модификатора tailrec, функция должна вызывать себя как последнюю операцию, которую она выполняет. Вы не можете использовать хвостовую рекурсию, когда есть код после рекурсивного вызова, в блоках try/catch/finally, или в открытых функциях. В настоящее время хвостовая рекурсия поддерживается Kotlin для JVM и Kotlin/Native.

См. также:

  • Встроенные функции

  • Расширяющие функции

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

Последнее изменение: 10 января 2023
Псевдонимы типов Функции высшего порядка и лямбда-выражения

© 2010–2023 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