Spec-Zone.ru › Kotlin 1.4

Функции

Объявление функций

Функции в 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) { /*...*/ }

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

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

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

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

fun read(
    b: Array<Byte>, 
    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)

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

Функции, помеченные ключевым словом 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))

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

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

Spec-Zone.ru

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