Функции
В Kotlin функции объявляются с помощью ключевого слова fun
.
fun double(x: Int): Int {
return 2 * x
}
Использование функций
При вызове функции используется традиционный подход:
val result = double(2)
Для вызова вложенной функции используется знак точки.
Stream().read() //создаёт экземпляр класса Stream и вызывает read()
Параметры
Параметры функции записываются аналогично системе обозначений в языке Pascal - имя: тип. Параметры разделены запятыми. Каждый параметр должен быть явно указан.
fun powerOf(number: Int, exponent: Int): Int { /*...*/ }
Вы можете использовать завершающую запятую при объявлении параметров функции.
fun powerOf(
number: Int,
exponent: Int, // завершающая запятая
) { /*...*/ }
Аргументы по умолчанию
Параметры функции могут иметь значения по умолчанию, которые используются в случае, если аргумент функции не указан при её вызове. Это позволяет снизить уровень перегруженности кода.
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) { /*...*/ } // значение по умолчанию указать нельзя
}
Если параметр по умолчанию предшествует параметру без значения по умолчанию, значение по умолчанию можно использовать только при вызове функции с именованными аргументами.
fun foo(
bar: Int = 0,
baz: Int,
) { /*...*/ }
foo(baz = 1) // Используется значение по умолчанию bar = 0
Но если последний аргумент после параметров по умолчанию - лямбда, вы можете передать её либо как именованный аргумент, либо за скобками.
fun foo(
bar: Int = 0,
baz: Int = 1,
qux: () -> Unit,
) { /*...*/ }
foo(1) { println("hello") } // Используется значение по умолчанию baz = 1
foo(qux = { println("hello") }) // Используется оба значения по умолчанию: bar = 0 и baz = 1
foo { println("hello") } // Используется оба значения по умолчанию: bar = 0 и 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` или `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
не стоит на последнем месте в
списке аргументов, значения для последующих параметров могут быть переданы только с использованием синтаксиса именованных
аргументов. В случае, если параметр является функцией, для этих целей можно вынести лямбду за фигурные скобки.
При вызове vararg
-функции вы можете передать аргументы один за другим, например asList(1, 2, 3)
, или, если у нас уже
есть необходимый массив элементов и вы хотите передать его содержимое в функцию, используйте оператор spread
(необходимо пометить массив знаком *
).
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
Если вы хотите передать массив примитивного типа в vararg
, вам необходимо
преобразовать его в обычный (типизированный) массив с помощью функции toTypedArray()
.
val a = intArrayOf(1, 2, 3) // IntArray - массив примитивного типа
val list = asList(-1, 0, *a.toTypedArray(), 4)
Инфиксная запись
Функции, помеченные ключевым словом infix
, могут вызываться с использованием инфиксной записи (без точки и
скобок для вызова). Инфиксные функции должны соответствовать следующим требованиям:
- Они должны являться функцией-членом класса или функцией расширения;
- В них должен использоваться только один параметр;
- Параметр не должен принимать переменное количество аргументов и не должен иметь значения по умолчанию.
infix fun Int.shl(x: Int): Int { /*...*/ }
// вызов функции, с использованием инфиксной записи
1 shl 2
// то же самое, что
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" // Верно
add("abc") // Верно
//add "abc" // Не верно: получатель должен быть указан
}
}
Область видимости функций
В 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())
}
Локальная функция может иметь доступ к локальным переменным внешних по отношению к ним функций (типа 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
Для более подробной информации о классах и их элементах см. Классы и Наследование.
Функции-обобщения
Функции могут иметь обобщённые параметры, которые задаются треугольными скобками и помещаются перед именем функции.
fun <T> singletonList(item: T): List<T> { /*...*/ }
Для более подробной информации см. Обобщения.
Функции с хвостовой рекурсией
Kotlin поддерживает стиль функционального программирования, известный как “хвостовая рекурсия”.
Это позволяет использовать циклические алгоритмы вместо рекурсивных функции, но без риска переполнения стэка. Когда
функция помечена модификатором tailrec
и её форма отвечает требованиям компилятора, он оптимизирует рекурсию, оставляя
вместо неё быстрое и эффективное решение этой задачи, основанное на циклах.
val eps = 1E-10 // этого достаточно, может быть 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 // этого достаточно, может быть 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
или в open
функциях. На данный
момент хвостовая рекурсия поддерживается только в backend виртуальной машины Java (JVM) и в Kotlin/Native.
См. также: