Spec-Zone.ru › Kotlin 1.9

Функции

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

См. также:

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

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

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

Последнее изменение: 19 июля 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