Функции

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

В Kotlin функции объявляются с помощью ключевого слова fun

fun double(x: Int): Int {
}

Применение функций

При вызове функции используется традиционный подход

val result = double(2)

Для вызова вложенной функции используется знак точки

Sample().foo() //создаёт экземпляр класса Sample и вызывает foo

Инфиксная запись

Функции так же могут быть вызваны при помощи инфиксной записи, при условии, что:

  • Они являются членом другой функции или расширения
  • В них используется один параметр
  • Когда они помечены ключевым словом infix
// Определяем выражение как Int
infix fun Int.shl(x: Int): Int {
...
}

// вызываем функцию, используя инфиксную запись

1 shl 2

// то же самое, что

1.shl(2)

Параметры

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

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

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

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

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) { ... }  // значение по умолчанию указать нельзя
}

Имена в названиях аргументов

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

Рассмотрим такую функцию

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

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

reformat(str)

Однако, при вызове этой функции без аргументов по умолчанию, получится что-то вроде

reformat(str, true, true, false, '_')

С названными аргументами мы можем сделать код намного более читаемым

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)

Или, если нам не нужны все эти аргументы

reformat(str, wordSeparator = '_')

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

Функции с возвращаемым типом Unit

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

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` или `return` необязательны
}

Указание типа 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 - это массив (Array)
        result.add(t)
    return result
}

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

val list = asList(1, 2, 3)

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

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

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

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

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

В Kotlin функции могут быть объявлены в самом начале файла. Подразумевается, что вам не обязательно создавать объект какого-либо класса, чтобы воспользоваться его функцией (как в Java, C# или Scala). В дополнение к этому, функции в языке Kotlin могут быть объявлены локально, как функции-члены (ориг. "member functions") и функции-расширения ("extension functions").

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

Koltin поддерживает локальные функции. Например, функции, вложенные в другие функции

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

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

Такие локальные функции могут иметь доступ к локальным переменным внешних по отношению к ним функций (типа closure). Таким образом, в примере, приведённом выше, 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() // создаёт инстанс класса Sample и вызвает его функцию foo

Для более подробной информации о классах и их элементах см. Классы

Функции-обобщения (Generic Functions)

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

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

Для более подробной информации об обобщениях см. Обобщения

Встроенные функции (Inline Functions)

О встроенных функциях рассказано здесь

Функции-расширения (Extension Functions)

О расширениях подробно написано в отдельной статье

Высокоуровневые функции и лямбды

О лямбдах и высокоуровневых функциях см. раздел Лямбды

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

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

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

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

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

Для соответствия требованиям модификатора tailrec, функция должна вызывать сама себя в качестве последней операции, которую она предпринимает. Вы не можете использовать хвостовую рекурсию, когда существует ещё какой-то код после вызова этой самой рекурсии. Также нельзя использовать её внутри блоков try/catch/finally. На данный момент, хвостовая рекурсия поддерживается только в backend виртуальной машины Java(JVM).