Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

fun

Коты забавные, поэтому ввели ключевое слово fun (есть спорное мнение, что на самом деле это сокращение от "function" для обозначения функций, которые являются аналогами методов в Java).

Объявление функции начинается с ключевого слова fun, затем идёт имя функции, в круглых скобках указываются параметры. Тип возвращаемого значения указывается после списка параметров и отделяется от него двоеточием. Функция всегда возвращает значение. Если вы сами не указали возвращаемое значение, то функция вернёт Unit, который схож с void, но является объектом.

Параметры в функциях объявляется немного иначе, чем в Java - сначала имя параметра, потом его тип.


fun add(x: Int, y: Int): Int {
	return x + y
}

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

Стандартный вывод "Hello Kitty" для Java-программы (не Android):


fun main(args: Array<String>)
    { println("Hello Kitty!")
}

Данная функция ничего не возвращает. Напишем другую функцию, возвращающую результат.


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) {
    println("Здравствуй $firstName $secondName")
}

Вызывая функцию, мы можем не соблюдать порядок параметров, если явно будем прописывать имена параметров.


sayHelloByName(secondName = "Котофеевич", firstName = "Котофей")

Несмотря на то, что мы поменяли местами параметры, итоговый результат всё равно будет работать правильно. Такой подход может пригодиться, когда вы не помните порядок и вам лень смотреть документацию.

Данный приём не сработает при работе с методами, написанными на Java. Поддержка именованных аргументов есть в Java 8, но Kotlin поддерживает совместимость с Java 6, поэтому приходится смириться. Возможно, в будущем, эта проблема решится автоматически, когда откажутся от поддержки старых версий.

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

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

Добавим в класс активности новую функцию для вывода всплывающего сообщения (в примере используется функция-расширение).


fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

Второй параметр использует значение по умолчанию и мы можем его не указывать при вызове. Вызываем функцию.


toast("Meow") // просто и аккуратно

toast("Meow-w-w", Toast.LENGTH_LONG) // используем второй параметр

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


fun sayHello(firstWord: String, secondWord: String = "Kitty", thirdWord: String) {
    println("$firstWord $secondWord $thirdWord")
}

Вызываем функцию с двумя параметрами, надеясь, что третий подставится самостоятельно. Но Kotlin не может решить, какой параметр пропущен.


sayHello("Hello", "Kitty") // не компилируется

В этом случае на помощь приходят именованные параметры.


sayHello("Hello", thirdWord = "Kitty")

Третий параметр теперь нам известен, опущенный параметр относится ко второму, оставшийся относится к первому.

У класса Thread имеется восемь конструкторов! Вы можете создавать гораздо удобные решения с параметрами по умолчанию.

Поскольку в Java нет понятия параметров по умолчанию, вам придётся явно указывать все значения при вызове функции Kotlin из Java-кода. В этом случае добавьте аннотацию @JvmOverloads, который создаст перегруженные версии методов, опуская каждый из параметрв по одному, начиная с последнего.

Если функция ничего не возвращает

Стоит немного рассказать о функциях, которые не возвращают никаких значений. В Java мы используем ключевое слово void для подобных случаев. В Kotlin был придуман новый тип Unit для подобных ситуаций. Получается, что функция всегда что-то возвращает, в нашем случае Unit, который мы никак не используем.


fun sayHello(name: String): Unit {
    println("Hello $name")
}

button.setOnClickListener {
    sayHello("Kitty")
}

Но Kotlin достаточно умен и понимает, что мы не хотим ничего возвращать. Поэтому мы можем сократить код.


fun sayHello(name: String) {
    println("Hello $name")
}

Переменное число параметров

В Java при вызове методов с разным числом аргументов использовалось троеточие (...). В Kotlin существует другой подход - ключевое слово vararg.


fun printNumbers(vararg integers: Int) {
    for (number in integers) {
        println("$number")
    }
}

Вызываем функцию с любым количеством аргументов.


printNumbers(1, 2, 3, 4, 5)

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

По сути vararg работает с массивом, но простое добавление массива Kotlin не пропустит. Следует использовать специальный оператор *.


val intArray: IntArray = intArrayOf(6, 7, 8, 9)
printNumbers(1, 2, 3, 4, 5, *intArray)

Анонимные функции

Функции можно не давать имя после ключевого слова fun и она станет анонимной.


val a: (Int) -> Int = fun(i: Int) = i + 3
println(a(4)) // 7

Анонимная функция прибавляет к заданному значению число 3.

В некоторых случаях анонимные функции удобны и полезны.

Вложенные (локальные) функции

Внутри одной функции можно создать ещё одну локальную функцию.


fun doIt(param: String) {
	//
	fun justDoIt(innerParam: String) {
		println(innerParam)
        println(param)
	}
}

Вложенная функция имеет доступ к переменным своей родительской функции.

Функции верхнего уровня

Функцию можно объявить в начале файла, не обязательно размещать его в теле класса. Это удобно, когда вам нужны методы, которые не относятся к конкретному классу или вы не хотите перезагружать имеющийся класс лишним кодом. Часто для этих целей программисты создавали отдельные классы со словом 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(...);

Дополнительное чтение

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

Реклама