Spec-Zone.ru › Kotlin 1.6

Функции

Функции 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"))

В JVM: синтаксис именованных аргументов нельзя использовать при вызове функций Java, так как байт-код 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. Кроме функций верхнего уровня, функции 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.

См. также:

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

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

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

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

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