Spec-Zone.ru › Kotlin 1.7

Функции

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

См. также:

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

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

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

Последнее изменение: 06 сентября 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