Kotlin передать функцию как параметр
На этом шаге мы закончим изучение этого вопроса .
Параметры функции
Далее идут параметры функции (рисунок 1).
Рис.1. Параметры функции
Параметры определяют имена и типы входных данных, необходимых функции для решения задачи. Функции могут требовать от нуля до нескольких и более параметров. Их количество зависит от того, для какой задачи они были спроектированы.
Чтобы функция formatHealthStatus могла определить, какое сообщение о состоянии здоровья выводить, необходимы переменные healthPoints и isBlessed , потому что условное выражение when должно проверить эти значения. Поэтому в объявлении функции formatHealthStatus эти две переменные указаны как параметры (рисунок 2):
Рис.2. Параметры функции
Для каждого параметра определяется также его тип. healthPoints должно быть целым числом, а isBlessed — булевым значением.
Обратите внимание, что параметры функции всегда доступны только для чтения, то есть в теле функции они не могут менять свои значения. Другими словами, в теле функции параметры — это val , а не var .
Тип возвращаемого значения
Многие функции создают выходные данные; это их основная задача — возвращать значение какого-то типа туда, откуда они вызваны. Последняя часть заголовка функции — это тип возвращаемого значения, который определяет тип выходных данных функции после завершения ее работы.
Тип String возвращаемого значения formatHealthStatus указывает, что функция возвращает строку (рисунок 3).
Рис.3. Тип возвращаемого значения
Тело функции
За заголовком следует тело функции, заключенное в фигурные скобки. Тело — это та часть функции, в которой происходит основное действие. Оно может содержать оператор return , определяющий возвращаемые данные.
В нашем случае команда выделения функции переместила объявление val healthStatus (код, который вы выбирали ранее при запуске программы) в тело функции formatHealthStatus .
Далее следует новая строка return healthStatus . Ключевое слово return указывает компилятору, что функция завершила работу и готова передать выходные данные. Выходные данные в нашем случае — healthStatus . То есть функция вернет значение переменной healthStatus — строку, основанную на логике определения healthStatus .
Область видимости функции
Обратите внимание, что переменная healthStatus объявляется и инициализируется внутри тела функции и ее значение возвращается в конце:
Рис.4. Объявление и возврат переменной healthStatus
Переменная healthStatus является локальной переменной, так как существует только в теле функции formatHealthStatus . Также можно сказать, что переменная healthStatus существует только в области видимости функции formatHealthStatus . Представьте себе область видимости как продолжительность жизни переменной.
Так как она существует только в области видимости функции, переменная healthStatus прекратит свое существование после завершения работы функции formatHealthStatus .
Это верно и для параметров функции: переменные healthPoints и isBlessed существуют только внутри области видимости функции и исчезают после выполнения функцией ее основной задачи. На 16 шаге вы видели пример переменной, которая не была локальной для функции или класса, — переменную уровня файла.
const val MAX_EXPERIENCE: Int = 5000 fun main()
Переменная уровня файла доступна из любого места в проекте (однако в объявление можно добавить модификатор видимости и изменить область видимости переменной). Переменные уровня файла существуют, пока не завершится выполнение всей программы.
Из-за разницы между локальными переменными и переменными уровня файла компилятор выдвигает разные требования к тому, когда им должно присваиваться начальное значение, или, говоря иначе, когда они должны инициализироваться.
Значения переменным уровня файла должны присваиваться сразу при объявлении, иначе код не скомпилируется. (Есть исключения, и мы с ними познакомимся чуть позже.) Это требование защитит вас от непредвиденного и нежелательного поведения, например, при попытке использовать переменную до ее инициализации.
Так как локальная переменная имеет более ограниченную область применения — внутри функции, в которой объявлена, — компилятор более снисходителен к тому, где она должна быть инициализирована, лишь бы она инициализировалась до ее использования. Это означает, что следующее определение верно:
fun main() < val name: String name = "Madrigal" var healthPoints: Int healthPoints = 89 healthPoints += 3 . . . . . >
Если код не обращается к переменной до ее инициализации, компилятор посчитает его допустимым.
На следующем шаге мы рассмотрим вызов функции .
Функциональное программирование
Одним из строительных блоков программы являются функции. Функция определяет некоторое действие. В Kotlin функция объявляется с помощью ключевого слова fun , после которого идет название функции. Затем после названия в скобках указывается список параметров. Если функция возвращает какое-либо значение, то после списка параметров через запятую можно указать тип возвращаемого значения. И далее в фигурных скобках идет тело функции.
fun имя_функции (параметры) : возвращаемый_тип
Например, определим и вызовем функцию, которая просто выводит некоторую строку на консоль:
fun main() < hello() // вызов функции hello hello() // вызов функции hello hello() // вызов функции hello >// определение функции hello fun hello()
Функции можно определять в файле вне других функций или классов, сами по себе, как например, определяется функция main. Такие функции еще называют функциями верхнего уровня (top-level functions).
Здесь кроме главной функции main также определена функция hello, которая не принимает никаких параметров и ничего не возвращает. Она просто выводит строку на консоль.
Функция hello (и любая другая определенная функция, кроме main) сама по себе не выполняется. Чтобы ее выполнить, ее надо вызвать. Для вызова функции указывается ее имя (в данном случае «hello»), после которого идут пустые скобки.
Таким образом, если необходимо в разных частях программы выполнить одни и те же действия, то можно эти действия вынести в функцию, и затем вызывать эту функцию.
Предача параметров
Через параметры функция может получать некоторые значения извне. Параметры указываются после имени функции в скобках через запятую в формате имя_параметра : тип_параметра . Например, определим функцию, которая просто выводит сообшение на консоль:
fun main() < showMessage("Hello Kotlin") showMessage("Привет Kotlin") showMessage("Salut Kotlin") >fun showMessage(message: String)
Функция showMessage() принимает один параметр типа String . Поэтому при вызове функции в скобках необходимо передать значение для этого параметра: showMessage(«Hello Kotlin») . Причем это значение должно представлять тип String, то есть строку. Значения, которые передаются параметрам функции, еще назвают аргументами.
Консольный вывод программы:
Hello Kotlin Привет Kotlin Salut Kotlin
Другой пример — функция, которая выводит данные о пользователе на консоль:
fun main() < displayUser("Tom", 23) displayUser("Alice", 19) displayUser("Kate", 25) >fun displayUser(name: String, age: Int)
Функция displayUser() принимает два параметра — name и age. При вызове функции в скобках ей передаются значения для этих параметров. При этом значения передаются параметрам по позиции и должны соответствовать параметрам по типу. Так как вначале идет параметр типа String , а потом параметр типа Int , то при вызове функции в скобках вначале передается строка, а потом число.
Аргументы по умолчанию
В примере выше при вызове функций showMessage и displayUser мы обязательно должны предоставить для каждого их параметра какое-то определенное значение, которое соответствует типу параметра. Мы не можем, к примеру, вызвать функцию displayUser, не передав ей аргументы для параметров, это будет ошибка:
displayUser()
Однако мы можем определить какие-то параметры функции как необязательные и установить для них значения по умолчанию:
fun displayUser(name: String, age: Int = 18, position: String=»unemployed») < println("Name: $name Age: $age Position: $position") >fun main()
В данном случае функция displayUser имеет три параметра для передачи имени, возраста и должности. Для первого параметр name значение по умолчанию не установлено, поэтому для него значение по-прежнему обязательно передавать значение. Два последующих — age и position являются необязательными, и для них установлено значение по умолчанию. Если для этих параметров не передаются значения, тогда параметры используют значения по умолчанию. Поэтому для этих параметров в принципе нам необязательно передавать аргументы. Но если для какого-то параметра определено значение по умолчанию, то для всех последующих параметров тоже должно быть установлено значение по умолчанию.
Консольный вывод программы
Name: Tom Age: 23 Position: Manager Name: Alice Age: 21 Position: unemployed Name: Kate Age: 18 Position: unemployed
Именованные аргументы
По умолчанию значения передаются параметрам по позиции: первое значение — первому параметру, второе значение — второму параметру и так далее. Однако, используя именованные аргументы, мы можем переопределить порядок их передачи параметрам:
fun main()
При вызове функции в скобках мы можем указать название параметра и с помощью знака равно передать ему нужное значение.
При этом, как видно из последнего случае, необязательно все аргументы передавать по имени. Часть аргументов могут передаваться параметрам по позиции. Но если какой-то аргумент передан по имени, то остальные аргументы после него также должны передаваться по имени соответствующих параметров.
Также если до обязательного параметра функции идут необязательные параметры, то для обязательного параметра значение передается по имени:
fun displayUser(age: Int = 18, name: String) < println("Name: $name Age: $age") >fun main()
Изменение параметров
По умолчанию все параметры функции равносильны val-переменным, поэтому их значение нельзя изменить. Например, в случае следующей функции при компиляции мы получим ошибку:
fun double(n: Int) < n = n * 2 // !Ошибка - значение параметра нельзя изменить println("Значение в функции double: $n") >
Однако если параметр предствляет какой-то сложный объект, то можно изменять отдельные значения в этом объекте. Например, возьмем функцию, которая в качестве параметра принимает массив:
fun double(numbers: IntArray)< numbers[0] = numbers[0] * 2 println("Значение в функции double: $") > fun main() < var nums = intArrayOf(4, 5, 6) double(nums) println("Значение в функции main: $") >
Здесь функция double принимает числовой массив и увеличивает значение его первого элемента в два раза. Причем изменение элемента массива внутри функции приведет к тому, что также будет изменено значение элемента в том массиве, который передается в качестве аргумента в функцию, так как этот один и тот же массив. Консольный вывод:
Значение в функции double: 8 Значение в функции main: 8
Kotlin передать функцию как параметр
Статья проплачена кошками — всемирно известными производителями котят.
Если статья вам понравилась, то можете поддержать проект.
- Именованные параметры
- Параметры по умолчанию
- Unit. Если функция ничего не возвращает
- Ключевое слово vararg — переменное число параметров
- Вложенные (локальные) функции
- Функции верхнего уровня
- Функция TODO()
- infix
- Имена функций в обратных кавычках
Коты забавные, поэтому ввели ключевое слово fun (есть спорное мнение, что на самом деле это сокращение от «function» для обозначения функций, которые являются аналогами методов в Java).
Объявление функции начинается с ключевого слова fun, затем идёт имя функции, в круглых скобках указываются параметры. Тип возвращаемого значения указывается после списка параметров и отделяется от него двоеточием. Функция всегда возвращает значение. Если вы сами не указали возвращаемое значение, то функция вернёт Unit, который схож с void, но является объектом.
Параметры в функциях объявляется немного иначе, чем в Java — сначала имя параметра, потом его тип.
fun add(x: Int, y: Int): Int
С функциями можно работать как с значениями — можно сохранить в переменной, передать в качестве параметра, возвратить из другой функции.
Стандартный вывод «Hello Kitty» для Kotlin-программы (Desktop, не Android):
fun main()
Данная функция ничего не возвращает. Напишем другую функцию, возвращающую результат.
fun max(a: Int, b: Int): Int < return if (a >b) a else b >
println(max(7, 2)) // выводит 7
Обратите внимание, что if является выражением в Kotlin, а не Java-оператором и соответствует тернарному оператору в Java:
(a > b) ? a : b
Простую функцию, в которой блок состоит из одной строки кода, можно переписать в одну строчку.
fun max(a: Int, b: Int): Int = if (a > b) a else b
Можно даже убрать возвращаемый тип. Гулять так гулять.
fun max(a: Int, b: Int) = if (a > b) a else b
Такой способ подходит только для функций, в которых Kotlin способен самостоятельно разобраться, чего хотел разработчик, т.е. с телом-выражением в правой части. В правой части мы вычисляем какой-то результат, который обычно передавали в return. Теперь мы можем отказаться от return и фигурных скобок, и сразу присваивать результат функции.
В других случаях (тело-блок) вы обязаны указывать возвращаемый тип и использовать инструкцию return.
Функции верхнего уровня можно импортировать для сокращения кода.
import strings.lastChar val cat = "Cat".lastChar()
Доступен вариант со звёздочкой.
import strings.* val c = "Cat".lastChar()
Можно даже изменить имя и создать псевдоним при помощи ключевого слова as. Этот вариант может оказаться полезным, если имеются несколько одинаковых названий функций из разных пакетов и хочется избежать путаницы и конфликтов.
import strings.lastChar as last val c = "Cat".last()
Именованные параметры
Мы привыкли, что при вызове метода следует соблюдать очерёдность параметров. С именованными параметрами такая необходимость отпала. Создадим новую функцию из двух параметров.
fun sayHelloByName(firstName: String, secondName: String)
Вызывая функцию, мы можем не соблюдать порядок параметров, если явно будем прописывать имена параметров.
sayHelloByName(secondName = "Котофеевич", firstName = "Котофей")
Несмотря на то, что мы поменяли местами параметры, итоговый результат всё равно будет работать правильно. Такой подход может пригодиться, когда вы не помните порядок и вам лень смотреть документацию.
Данный приём не сработает при работе с методами, написанными на Java. Поддержка именованных аргументов есть в Java 8, но Kotlin поддерживает совместимость с Java 6, поэтому приходится смириться. Возможно, в будущем, эта проблема решится автоматически, когда откажутся от поддержки старых версий.
Параметры по умолчанию
Очень удобная функциональность — создание параметров по умолчанию. Если вы предполагаете, что какой-то параметр будет часто использовать какое-то конкретное значение, то мы можем сразу его указать. При вызове функции мы можем опустить этот параметр, он применится автоматически. Если нам нужно указать другое значение, то параметр добавим.
Добавим в класс активности новую функцию для вывода всплывающего сообщения (в примере используется функция-расширение).
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT)
Второй параметр использует значение по умолчанию и мы можем его не указывать при вызове. Вызываем функцию.
toast("Meow") // просто и аккуратно toast("Meow-w-w", Toast.LENGTH_LONG) // используем второй параметр
С параметрами по умолчанию нужно быть внимательными, возможна ситуация, когда Kotlin не поймёт, что вы от него хотите. Создадим функцию из трёх параметров, один из них будет иметь значение по умолчанию.
fun sayHello(firstWord: String, secondWord: String = "Kitty", thirdWord: String)
Вызываем функцию с двумя параметрами, надеясь, что третий подставится самостоятельно. Но Kotlin не может решить, какой параметр пропущен.
sayHello("Hello", "Kitty") // не компилируется
В этом случае на помощь приходят именованные параметры.
sayHello("Hello", thirdWord = "Kitty")
Третий параметр теперь нам известен, опущенный параметр относится ко второму, оставшийся относится к первому.
У класса Thread имеется восемь конструкторов! Вы можете создавать гораздо удобные решения с параметрами по умолчанию.
Поскольку в Java нет понятия параметров по умолчанию, вам придётся явно указывать все значения при вызове функции Kotlin из Java-кода. В этом случае добавьте аннотацию @JvmOverloads, который создаст перегруженные версии методов, опуская каждый из параметров по одному, начиная с последнего.
Unit. Если функция ничего не возвращает
Стоит немного рассказать о функциях, которые не возвращают никаких значений. В Java мы используем ключевое слово void для подобных случаев. В Kotlin был придуман новый тип Unit для подобных ситуаций. Получается, что функция всегда что-то возвращает, в нашем случае Unit, который мы никак не используем.
fun sayHello(name: String): Unit < println("Hello $name") >button.setOnClickListener
Но Kotlin достаточно умен и понимает, что мы не хотим ничего возвращать. Поэтому мы можем сократить код.
fun sayHello(name: String)
Можно сократить код, убрав фигурные скобки, так как у функции только одно выражение.
fun sayHello(name: String) = println("Hello $name")
Ключевое слово vararg — переменное число параметров
В Java при вызове методов с разным числом аргументов использовалось троеточие (. ). В Kotlin существует другой подход — ключевое слово vararg.
fun printNumbers(vararg integers: Int) < for (number in integers) < println("$number") >>
Вызываем функцию с любым количеством аргументов.
printNumbers(1, 2, 3, 4, 5) printNumbers(4)
Если функция имеет и другие параметры, то они должны быть раньше vararg. Можно обойти это правило, если использовать именованные параметры, но лучше избегать таких ситуаций.
По сути vararg работает с массивом, но простое добавление массива Kotlin не пропустит. Следует использовать специальный оператор *.
val intArray: IntArray = intArrayOf(6, 7, 8, 9) printNumbers(1, 2, 3, 4, 5, *intArray)
Вложенные (локальные) функции
Внутри одной функции можно создать ещё одну локальную функцию.
fun doIt(param: String) < // fun justDoIt(innerParam: String) < println(innerParam) println(param) >>
Вложенная функция имеет доступ к переменным своей родительской функции.
Создадим функцию, которая выводим имя кота в верхнем регистре. Заодно создадим вложенную функцию, которая подсчитывает длину имени кота.
fun getCat(name: String) < fun makeStrange(): Int < return name.length * 2 >println(name.uppercase() + makeStrange()) > // вызываем функцию getCat("barsik") // BARSIK12 getCat("vaska") // VASKA10
Функции верхнего уровня
Функцию можно объявить в начале файла, не обязательно размещать его в теле класса. Это удобно, когда вам нужны методы, которые не относятся к конкретному классу или вы не хотите перезагружать имеющийся класс лишним кодом. Часто для этих целей программисты создавали отдельные классы со словом Util.
Размещая код метода за пределами класса, вы избегаете лишней вложенности. Они по-прежнему являются членами пакета, прописанного в файле и могут импортироваться при использовании в других пакетах.
Таким образом можно создать новый файл без всяких классов, указав только пакет.
// файл cats.kt package kitten fun someFun(. ): String
Kotlin незаметно для вас создаст класс CatsKt по имени файла и все функции скомпилирует в статические методы. Если будете вызывать функцию в Java-коде, то это будет выглядеть следующим образом.
import kitten.CatsKt; . CatsKt.someFun(. );
Если имя класса вас не устраивает, то добавьте аннотацию @JvmName перед именем пакета.
@file:JmvName("CatFunctions") package kitten
Тогда вызов в Java-коде будет другим.
import kitten.CatFunctions; . CatFunctions.someFun(. );
Функция TODO()
В стандартную библиотеку Kotlin входит функция TODO() (надо сделать). Её описание выглядит следующим образом.
/** * Всегда возбуждает [NotImplementedError], сигнализируя, что операция не реализована. */ public inline fun TODO(): Nothing = throw NotImplementedError()
Функция TODO() возбуждает исключение, т.е. вызов функции гарантированно завершится ошибкой — она возвращает тип Nothing. Считайте функцию временной заглушкой. Разработчик знает, что некоторая функция должна вернуть строку или другой объект, но пока отсутствуют другие функции, необходимые для ее реализации. Создадим для примера две функции.
fun shouldReturnAString(): String < TODO("implement the string building functionality here to return a string") >fun shouldReturnACat(): Cat
Обратите внимание, что возвращаемое значение для shouldReturnAString() — это String, но на самом деле функция ничего не возвращает. Аналогично у shouldReturnACat().
Возвращаемый тип Nothing у TODO() показывает компилятору, что функция гарантированно вызовет ошибку, поэтому проверять возвращаемое значение после TODO() не имеет смысла, так как shouldReturnAString() и shouldReturnACat() ничего не вернут. Компилятор не будет ругаться, а разработчик может продолжать разработку, отложив на потом реализацию функции-заглушки.
Функцию можно вызвать без аргументов. Код, который будет следовать за функцией, будет недостижим.
fun shouldReturnACat(): Cat < TODO() println("миссия невозможна") // этот код не будет вызван >
infix
Существует специальная форма вызова метода — инфиксный вызов. В инфиксном вызове имя метода помещается между именем целевого объекта и параметром без дополнительный разделителей.
Например, в ассоциативных списках часто используют следующий приём.
// инфиксная нотация val map = mapOf(1 to "one", 3 to "three", 9 to "nine") println(map)
Пример можно заменить на более традиционный.
// обычный способ val map2 = mapOf(1.to("one"), 2.to("two"), 5.to("five")) println(map2)
Инфиксную форму можно применять к обычным методам и функциям-расширениям, имеющим один обязательный параметр. Для этого в объявление функции нужно добавить модификатор infix.
Имена функций в обратных кавычках
Можно объявить или вызвать функцию с именем, содержащим нестандартные символы. Для этого достаточно заключить имя в обратные кавычки `. Например, объявим функцию:
fun `12!cat`() = println("I am a cat!")
`12!cat`()
Данная возможность нужна, чтобы поддерживать совместимость с Java в тех моментах, когда встречаются зарезервированные ключевые слова. Использование обратных кавычек позволяют избежать несовместимости в случаях, если это необходимо. На практике такое почти не встречается.
На данный момент под Android такой способ не работает, студия будет ругаться.
Kotlin передать функцию как параметр
Функции высокого порядка (high order function) — это функции, которые либо принимают функцию в качестве параметра, либо возвращают функцию, либо и то, и другое.
Функция как параметр функции
Чтобы функция могла принимать другую функцию через параметр, этот параметр должен представлять тип функции:
fun main() < displayMessage(::morning) displayMessage(::evening) >fun displayMessage(mes: () -> Unit) < mes() >fun morning() < println("Good Morning") >fun evening()
В данном случае функция displayMessage() через параметр mes принимает функцию типа () -> Unit , то есть такую функцию, которая не имеет параметров и ничего не возвращает.
fun displayMessage(mes: () -> Unit)При вызове этой функции мы можем передать этому параметру функцию, которая соответствует этому типу:
displayMessage(::morning)Рассмотрим пример параметра-функции, которая принимает параметры:
fun main() < action(5, 3, ::sum) // 8 action(5, 3, ::multiply) // 15 action(5, 3, ::subtract) // 2 >fun action (n1: Int, n2: Int, op: (Int, Int)-> Int) < val result = op(n1, n2) println(result) >fun sum(a: Int, b: Int): Int < return a + b >fun subtract(a: Int, b: Int): Int < return a - b >fun multiply(a: Int, b: Int): Int
Здесь функция action принимает три параметра. Первые два параметра - значения типа Int. А третий параметр представляет функцию, которая имеет тип (Int, Int)-> Int , то есть принимает два числа и возвращает некоторое число.
В самой функции action вызываем эту параметр-функцию, передавая ей два числа, и полученный результат выводим на консоль.
При вызове функции action мы можем передать для ее третьего параметра конкретную функцию, которая соответствует этому параметру по типу:
action(5, 3, ::sum) // 8 action(5, 3, ::multiply) // 15 action(5, 3, ::subtract) // 2Возвращение функции из функции
В более редких случаях может потребоваться возвратить функцию из другой функции. В этом случае для функции в качестве возвращаемого типа устанавливается тип другой функции. А в теле функции возвращается лямбда выражение. Например:
fun main() < val action1 = selectAction(1) println(action1(8,5)) // 13 val action2 = selectAction(2) println(action2(8,5)) // 3 >fun selectAction(key: Int): (Int, Int) -> Int < // определение возвращаемого результата when(key)< 1 ->return ::sum 2 -> return ::subtract 3 -> return ::multiply else -> return ::empty > > fun empty (a: Int, b: Int): Int < return 0 >fun sum(a: Int, b: Int): Int < return a + b >fun subtract(a: Int, b: Int): Int < return a - b >fun multiply(a: Int, b: Int): Int
Здесь функция selectAction принимает один параметр - key, который представляет тип Int . В качестве возвращаемого типа у функции указан тип (Int, Int) -> Int . То есть selectAction будет возвращать некую функцию, которая принимает два параметра типа Int и возвращает объект типа Int.
В теле функции selectAction в зависимости от значения параметра key возвращается определенная функция, которая соответствует типу (Int, Int) -> Int .
Далее в функции main определяется переменная action1 хранит результат функции selectAction . Так как selectAction() возвращает функцию, то и переменная action1 будет хранить эту функцию. Затем через переменную action1 можно вызвать эту функцию.
Поскольку возвращаемая функция соответствует типу (Int, Int) -> Int , то при вызове в action1 необходимо передать два числа, и соответственно мы можем получить результат и вывести его на консоль.