Kotlin как вынести функции в отдельный файл
Перейти к содержимому

Kotlin как вынести функции в отдельный файл

  • автор:

Строки

Обратите внимание, что мы добавили знак доллара к имени переменной и используем её прямо в строке. В Java нам пришлось бы разбивать строку и соединять её с помощью конкатенации.

 // Java println("Привет " + catName + "! Как дела?"); 

Кстати, такой код тоже будет работать, но первый способ гораздо удобнее.

Мы понимаем, что речь идёт о строке, поэтому можно было написать код без указания типа.

 val catName = "Барсик" 

Если вам вдруг захочется вывести символ доллара, то используйте экранирование.

 println("Привет \$catName! Как дела?") // выводится: Привет $catName! Как дела? 

Мы использовали так называемый «строковый шаблон». За кулисами происходит соединение при помощи StringBuilder, но более удобным и быстрым способом.

Длину строки можно вычислить через функцию count().

 val murzik = "Мурзик" println(murzik.count()) 

Функции-расширения

Разработчики JetBrains добавили множество готовых функций-расширений для многих классов, в том числе и для строк. Найти их можно в файле String.kt (в студии дважды нажмите клавишу Shift и в поисковой строке наберите имя данного файла для просмотра исходника).

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

Пробежаться по строке

Строку можно рассматривать как массив символов.

 val cat = "Барсик" val character = cat[2] println(character) // выводит р 

Пробежаться по всей строке без использования индекса.

 val cat = "Барсик" for(char in cat)

Пробежаться по всей строки с использованием индекса.

 val cat = "Барсик" for (char in cat.indices)

Саму строку можно предварительно явно перевести в массив символов.

 for(char in cat.toCharArray())

Можно вызывать forEach.

 cat.forEach < char ->println(char) > 

Если вам нужен не только символ строки, но и его индекс, то вызываем forEachIndexed.

 cat.forEachIndexed println("Index $index Character $char")> // Результат Index 0 Character Б Index 1 Character а Index 2 Character р Index 3 Character с Index 4 Character и Index 5 Character к 

Если нужно получить отдельный символ строки по индексу без перебора всей строки, то просто указываем нужный индекс.

 val cat = "Барсик" val index = 5 println("Character at index $index in $cat is $") 

Если вы укажете несуществующий индекс, то получите исключение StringIndexOutOfBoundsException, поэтому делайте проверку.

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

 val a: Int = 9 val b: Int = 1 fun main(args: Array) < println("Осталось жизней: $") > 

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

Выведем второй элемент массива.

 fun main(args: Array) < val names = arrayListOf("Мурзик") names.add("Васька") names.add("Барсик") println("Кота зовут $") > 

Опять используем знак доллара и фигурные скобки.

Можно даже использовать условие.

 val count = 9 print("value of count is $") 

Многострочный текст можно создать, используя тройные кавычки. Больше нам не нужны символы перевода строки \n, а также символы типа \t, \b, \r и т.д.

 val multipleStringLines = """ У лукоморья дуб зелёный; Златая цепь на дубе том: И днём и ночью кот учёный Всё ходит по цепи кругом; Идёт направо - песнь заводит, Налево - сказку говорит. """ 

Метод trimMargin() позволяет убрать «пустые» символы из текста по разделителю | (по умолчанию):

 val myString = """This is the first line |This is the second line |This is the third line |And fourth line """ println(myString.trimMargin()) 

На выходе получим.

 This is the first line This is the second line This is the third line And fourth line 

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

 trimMargin(">") 

Если первая и последняя строки длинного предложения пустые, то они будут удалены и строки.

Можем делать со строкой знакомые операции в удобной обёртке.

 val cat = "Барсик" println(cat.reversed()) // кисраБ println(cat.takeLast(2)) // ик val kitty /cdn-cgi/l/email-protection" data-cfemail="95e7effcfed5f2f8f4fcf9bbf6faf8">[email protected]" println(kitty.substringBefore("@")) // murzik 

Конвертируем строку в число

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

 val str = "123" print(str.toLong()) 

Если нужно обработать ситуацию с null, то используйте другой метод.

 val str = "123.4" println(str.toLongOrNull()) // вернёт null 

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

 val str = "11111111" print(str.toLongOrNull(2)) //255 val str = "105" print(str.toLongOrNull(8)) //69 

Также есть схожие методы toShort(), toShortOrNull(), toInt(), toIntOrNull(), toFloat(), toDouble() и т.д. Часть из них поддерживает перегруженные версии с другой системой счисления, но проверяйте поддерживаемые системы.

Не совсем про числа, но можно сконвертировать в булево значение.

 val str = "false" println(str.toBoolean()) 

Дополняем строку символами

Можно дополнить строку символами с самого начала или в конце.

 val name = "Barsik" val pad = name.padStart(10, '#') println(pad) // ####Barsik val name = "Barsik" val pad = name.padEnd(10, '*') println(pad) // Barsik**** 

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

Подстроки

 // подстрока с указанного индекса val result = "developer.alexanderklimov.ru".substring(10) // alexanderklimov.ru // подстрока до первого указанного разделителя val first = "developer.alexanderklimov.ru".substringBefore('.') // developer // подстрока после первого указанного разделителя val last = "developer.alexanderklimov.ru".substringAfter('.') // alexanderklimov.ru // подстрока после последнего указанного разделителя val last = "developer.alexanderklimov.ru".substringAfterLast('.') // ru // подстрока до последнего указанного разделителя val beforeLast = "developer.alexanderklimov.ru".substringBeforeLast('.') // developer.alexanderklimov 

Ещё пример для получения адреса сайта без http://.

 val superSite = "http://developer.alexanderklimov.ru" val index = superSite.lastIndexOf('/') println("Индекс нужного нам символа: $index") println(superSite.substring(index + 1)) // developer.alexanderklimov.ru // другой вариант println(superSite.substringAfterLast("/")) 

Встроенные функции

Kotlin содержит множество встроенных удобных функций для работы со строками. Часть из них уже использовалась в примерах выше. Упомянем ещё несколько полезных функций.

 val blank = " ".isBlank() // true, если пустая строка или пустые символы пробела, табуляции и т.п. // индекс последнего символа val lastIndex = "Кот Мурзик".lastIndex // 9 // переводим в верхний регистр первый символ строки // decapitalize() выполняем обратную задачу val capitalize = "кот Мурзик".capitalize() val withSpaces = "1".padStart(2) // добавляем пробел перед строкой val endZeros = "1".padEnd(3, '0') // "100" добавляем нули в конец val dropStart = "Kotlin".drop(2) // "tlin" убираем первые символы в указанном количестве val dropEnd = "Kotlin".dropLast(3) // "Kot" убираем последние символы в указанном количестве // возвращаем строку без первого символа, который удовлетворяет условию val string = "Мурзик" val result = string.dropWhile < it == 'М' >textView.text = result // урзик // возвращаем строку без последнего символа, который удовлетворяет условию val string = "Мурзик" val result = string.dropLastWhile < it == 'к' >textView.text = result // Мурзи // разбиваем на массив строк "A\nB\nC".lines() // [A, B, C] "ABCD".zipWithNext() // [(A, B), (B, C), (C, D)] // удаляем символы из заданного диапазона val string = "Кот, который гулял сам по себе" val result = string.removeRange( 3..28 // range ) // Функции removeXXX() хороши для строк в виде ##Cat##, чтобы убрать лишние символы // удаляем префикс из строки val string = "Кот, который гулял сам по себе" val result = string.removePrefix("Кот") // удаляем суффикс из строки val string = "Кот, который гулял сам по себе" val result = string.removeSuffix("себе") // удаляем заданный разделитель, который должен окружать строку с начала и с конца val string = "та, тра-та-та, мы везём с собой кота" val result = string.removeSurrounding( "та" // delimiter ) textView.text = result // , тра-та-та, мы везём с собой ко // Также можно указать разные начало и конец, которые окружают строку val string = "Тра-та-та, тра-та-та, мы везём с собой кота" val result = string.removeSurrounding( "Тра-", // prefix " кота" // suffix ) textView.text = result // та-та, тра-та-та, мы везём с собой // Добавляем отступы при явном переводе на новую строку val string = "Какой-то длинный текст, \nсостоящий из имён котов: " + "\nВаська" + "\nБарсик" + "\nРыжик" val result = string.prependIndent( " " // indent ) // Разбиваем символы на две группы. // В первую группу попадут символы в верхнем регистре, во вторую - символы в нижнем регистре val string = "Кот Васька и кот Мурзик - Друзья!" val result: Pair = string.partition < it.isUpperCase() >textView.text = result.first + ":" + result.second //КВМД:от аська и кот урзик - рузья! // Разбиваем строку на список строк. В качестве разделителя - перевод на новую строку val string = "Кот Васька\nКотМурзик\nКот Мурзик" // Split string into lines (CRLF, LF or CR) val lines: List = string.lines() textView.text = "Кол-во строк: $" lines.forEach < textView.append("\n" + it) >// Разбиваем строку на список строк с указанным числом символов. В последней строке может выводиться остаток val string = "Тра-та-та, тра-та-та, мы везём с собой кота" val list:List = string.chunked(11) list.forEach < textView.append("$it\n") >/* Тра-та-та, тра-та-та, мы везём с собой кота */ // Содержит ли строка только цифры (используем предикат) val string = "09032020" // Returns true if all characters match the given predicate. val result: Boolean = string.all < it.isDigit() >textView.append("Is the string $string contain only digit? $result") // Содержит ли строка хотя бы одну цифру (используем предикат) val string = "3 кота" // Returns true if at least one character matches the given predicate. val result: Boolean = string.any() < it.isDigit() >textView.append("Is the text \"$string\" contain any digit? $result") // Сравниваем две строки с учётом регистра val string1 = "This is a sample string." val string2 = "This is a SAMPLE string." if (string1.compareTo(string2, ignoreCase = true) == 0) < textView.append("\n\nBoth strings are equal, ignoring case.") >else

Можно фильтровать данные с помощью filter(). Допустим, мы хотим извлечь только цифры из строки.

 val string = "9 жизней (2016) - Nine Lives - информация о фильме" val filteredText = string.filter < it.isDigit() >textView.text = filteredText // 92016 

Если хочется решить обратную задачу и получить только символы, но не цифры — то достаточно вызвать filterNot().

 val filteredText = string.filterNot

replace()/replaceRange()

Для замены отдельных символов или строк используется функция replace(). Заменим отдельные символы в строке.

 val string = "Кит Кишка" val result = string.replace( 'и', // old char 'о', // new char true // ignore case Boolean = false ) textView.text = result // Кот Кошка 

Можно менять подстроки.

 val result = string.replace( "Собака", // old value "Кот", // new value true // ignore case ) textView.text = result // Кот - друг человека 

Вариант replace() с регулярным выражением. Создадим функцию перевода строки на «драконий» язык. В результате будет создана новая строка с нужными символами.

 private fun toDragonSpeak(phrase: String) = phrase.replace(Regex("[aeiou]")) < when (it.value) < "a" ->"4" "e" -> "3" "i" -> "1" "o" -> "0" "u" -> "|_|" else -> it.value > > println(toDragonSpeak("Kitten")) // K1tt3n 

Можно заменять подстроки через replaceRange(), указывая нужный диапазон. Существуют две версии этого способа.

 val string = "Тра-та-та, тра-та-та, мы везём с собой ежа" // The end index of the range is included // in the part to be replaced. val result = string.replaceRange( 39..40, // range "кот" // replacement ) textView.append(result) val result2 = string.replaceRange( 39, // start index 41, // end index "кот" // replacement ) textView.append("\n") textView.append(result2) 

Конвертируем строку в дату

 import java.time.LocalDate var parsedDate = LocalDate.parse("2020-07-27") println(parsedDate) 

Есть также вариант с использованием DateTimeFormatter.

 import java.time.LocalDate import java.time.format.DateTimeFormatter var parsedDate = LocalDate.parse("Wednesday, July 27, 2020", DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy")) println("Wednesday, July 27, 2020 : " + parsedDate) parsedDate = LocalDate.parse("July 27, 2020", DateTimeFormatter.ofPattern("MMMM dd, yyyy")) println("July 27, 2020 : " + parsedDate) parsedDate = LocalDate.parse("14/02/2020", DateTimeFormatter.ofPattern("dd/MM/yyyy")) println("14/02/2020 : "+parsedDate) parsedDate = LocalDate.parse("27 July,2019", DateTimeFormatter.ofPattern("dd MMMM,yyyy")) println("27 July,2019 : " + parsedDate) parsedDate = LocalDate.parse("11th April,2012", DateTimeFormatter.ofPattern("dd'th' MMMM,yyyy")) println("11th April,2012 : " + parsedDate) parsedDate = LocalDate.parse("27 Feb, 2001", DateTimeFormatter.ofPattern("dd MMM, yyyy")) println("27 Feb, 2001 : " + parsedDate) 

StringBuilder

У класса StringBuilder в Kotlin есть отдельная функция buildString(), которая поможет сократить количество кода для получения строки.

 fun printAlphabet() = buildString

Функции

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

fun double(x: Int): Int

Использование функций

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

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")) 

On the JVM: You can’t use the named argument syntax when calling Java functions because Java bytecode does not > always preserve the names of function parameters. —>

В 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 asList(vararg ts: T): List < val result = ArrayList() for (t in ts) // ts - это массив (Array) result.add(t) return result > 

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

val list = asList(1, 2, 3) 

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

Только один параметр может быть помечен как 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) 

Infix function calls have lower precedence than arithmetic operators, type casts, and the `rangeTo` operator. > The following expressions are equivalent: > * `1 shl 2 + 3` is equivalent to `1 shl (2 + 3)` > * `0 until n * 2` is equivalent to `0 until (n * 2)` > * `xs union ys as Set ` is equivalent to `xs union (ys as Set )` > > On the other hand, an infix function call’s precedence is higher than that of the boolean operators `&&` and `||`, `is`- > and `in`-checks, and some other operators. These expressions are equivalent as well: > * `a && b xor c` is equivalent to `a && (b xor c)` > * `a xor b in c` is equivalent to `(a xor b) in c` —>

  • 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) .
  • 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) < 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() 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 singletonList(item: T): List  < /*. */ >

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

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

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.

См. также:

  • Встроенные функции,
  • Функции-расширения,
  • Высокоуровневые функции и лямбды.

© 2015—2023 Open Source Community

Кotlin и Android - видимость функции из другого файла, а не MainActivity

некоторые функции хочу поместить в отдельный файл MyFunc.kt, а не в MainActivity.kt. Как сделать так, чтобы функции из MyFunc.kt можно было выбирать в конструкторе кнопок Design и вешать на свойство OnClick?

Отслеживать
17.9k 11 11 золотых знаков 25 25 серебряных знаков 56 56 бронзовых знаков
задан 31 июл 2021 в 16:46
117 1 1 серебряный знак 9 9 бронзовых знаков
Импортировать..
31 июл 2021 в 18:13

Добавьте подробностей в ваш вопрос, объясните, что такое "конструктор кнопок Design", как это выглядит? Приведите пример кода, который вы пытались написать, и что конкретно у вас не получилось сделать?

1 авг 2021 в 17:25

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Пусть файл MyFunc.kt с функцией hello() лежит в пакете com.example.utils :

package com.example.utils fun hello()

Чтобы использовать эту функцию в MainActivity.kt , сделаем импорт этой функции:

package com.example import com.example.utils.hello class MainActivity : AppCompatActivity() < override fun onCreate(savedInstanceState: Bundle?) < . val button = findViewById(. ) button.setOnClickListener < hello() >> > 

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

Магия функций в Kotlin

Android_Deep_16.3-5020-d224fb.png

Kotlin — простой и совместимый с Java язык программирования, который сокращает время написания кода за счет более коротких конструкций. В этой статье мы расскажем про несколько популярных способов «магического» применения функций в Kotlin.

Extension Functions

Первый способ — расширение классов без наследования. Он позволяет, не меняя класс String и использующие его пакеты, расширить данный класс путём добавления нового метода или свойства.

Представьте, что у нас есть метод deleteSpaces() :

 
private fun String.deleteSpaces(): String  return this.replace(" ", "") > 

Мы можем использовать этот метод таким образом, как будто он — это часть класса String. Пользователь увидит это вот так:

 
println("Hel lo, Wor ld".deleteSpaces()) // Hello,World 

После выполнения компиляции этот метод уже будет выглядеть приблизительно так (код упрощён, главное — понять суть):

 
private static final String deleteSpaces(@NotNull String receiver)  return receiver.replace(" ", ""); > 

Следовательно, делаем вывод, что внутри метода deleteSpaces() у нас присутствует доступ лишь к публичным полям и методам класса, в результате чего инкапсуляция не нарушается.

Кроме Extension Functions, в Котлин по аналогии могут быть и Extension Properties:

 
private val String.first: Char get()  return this[0] > print("Kotlin".first) // K 

Большая часть «магических» применений лямбд и функций в Котлин, по сути, являются не более, чем синтаксическим сахаром, но зато каким удобным!

Анонимные функции и лямбда-функции

Анонимные и лямбда-функции в Kotlin — это функции без имени, однако при этом их можно использовать в качестве объектов. И писать их можно непосредственно внутри выражения без отдельного объявления.

Рассмотрим синтаксис лямбда-выражения:

 
 аргументы -> возвращаемый_тип тело_функции > 

Что касается анонимной функции, то синтаксис её декларации абсолютно совпадает с синтаксисом обычной функции, просто у первой нет имени.

 
fun defaultFun(x: Int, y: Int): Int = x + y // Именованная функция val f = fun(x: Int, y: Int): Int = x + y // Анонимная функция 

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

Функции высшего порядка

Речь идёт о функции, принимающей в виде одного из аргументов другую функцию, включая лямбду либо анонимную функцию. Ярчайший пример применения – callback.

Представьте, что у нас есть функция высшего порядка longWork() :

 
private fun longWork(callback: (arg1: Int, arg2: String) -> Unit)  val result = doSomething() callback(result, "Kotlin > Java") // вызов callback > 

Эта функция принимает в виде аргумента функцию callback() , однако вызывает её лишь после функции doSomething() :

 
longWork( arg1, arg2 -> Unit print("callback runned") >) 

Как видите, мы вызываем функцию longWork() , передавая ей в виде аргумента лямбда-функцию, которую она вызовет позже. Язык программирования Котлин даёт возможность выносить лямбду за скобки в том случае, если она является последним аргументом функции, а также вообще убирать скобки, если лямбда — единственный аргумент. Кроме этого, чаще всего мы можем убрать тип возвращаемого значения, заменив аргументы на _ в том случае, если они не используются. Вот как выглядит укороченный вариант кода:

 
longWork  _, _ -> print("callback runned") > 

Всё это внешне уже напоминает не функцию высшего порядка, а языковую конструкцию, как, к примеру synchronized в Java. Кстати, synchronized в Котлин построен именно как функция высшего порядка.

Всё это весьма удобно при создании DSL – предметно-ориентированных языков. Допустим, того же Anko (Android UI прямо в Kotlin с сохранением удобства XML-разметки), или kotlinx.html (по аналогии с Anko), или Gradle Kotlin DSL (Gradle-скрипты на Котлин).

Давайте посмотрим на HTML-страницу на Kotlin:

 
System.out.appendHTML().html  body  div  a("http://kotlinlang.org")  target = ATarget.blank +"Main site" > > > > 

Вывод в stdout будет следующим:

 
html> body> div>a href="http://kotlinlang.org" target="_blank">Main sitea>div> body> html> 

Главный плюс этого DSL заключается в том, что в Kotlin существуют переменные, которые можно использовать для генерации динамической страницы (в отличие, скажем, от декларативного HTML). И всё это красивее и удобнее классической генерации страниц посредством конкатенации множества строк. Но вообще на практике для генерации HTML-разметки применяют иные методы, а этот мы показали лишь в качестве примера DSL в Kotlin.

Кроме того, возможно использование функций высшего порядка в качестве аналога Streams API из Java:

 
listOf(1, 2, 3, 4, 5) .filter  n -> n % 2 == 1 > // 1, 3, 5 .map  n -> n * n > // 1, 9, 25 .drop(1) // 9, 25 .take(1) // 9 

Ещё несколько слов о Kotlin

В Kotlin, в отличие от Джавы, присутствуют перегружаемые операторы. К примеру, если у класса есть метод plus() , мы можем вызвать его оператором +, а метод get() – с помощью оператора []. Это во-первых.

Во-вторых, функции в Котлин можно пометить явно как inline либо noinline. Такой модификатор говорит компилятору, стоит ли заинлайнить функцию в целях повышения производительности либо нет. Но тут есть нюанс, связанный с разным поведением return в inline- и noinline-функциях.

В inline-функциях return будет произведен из ближайшей по области видимости noinline-функции. А в noinline – непосредственно из самой функции. Данную проблему решают «именованные return».

Чтобы сделать return из лямбды, которую мы передаём в вышеописанном примере в apply() , мы можем задействовать return@apply.

Кстати, именованными могут быть не только return, но и break, continue. Кроме того, есть возможность создавать и свои метки:

 
loop@ for (i in 1..100)  for (j in 1..100)  if (. ) break@loop > > 

Также есть модификатор функции tailrec, который сообщает компилятору заменить рекурсию в функции на цикл, если функция написана в функциональном формате return if-then-else:

 
tailrec fun findFixPoint(x: Double = 1.0): Double = when  x == cos(x) -> x else -> findFixPoint(cos(x)) > // При компиляции будет заменена на fun findFixPoint(): Double  var x = 1.0 while (true)  val y = cos(x) if (x == y) return x x = y > > 

В-третьих, в ситуации, когда метод требует в виде аргументов объект, который должен быть унаследован от класса/интерфейса с одним абстрактным методом, в эту функцию мы можем передать лямбду либо анонимную функцию, а компилятор самостоятельно создаст анонимный класс с переопределением абстрактного метода на нашу лямбду. К примеру, в стандартной Android-библиотеке существует метод public void setOnClickListener(View.OnClickListener l) , в котором OnClickListener представляет собой интерфейс с единственным методом onClick(View v) .

Кстати, лямбда, переданная в качестве setOnClickListener < doSomething() >, скомпилируется в анонимный класс, реализующий интерфейс OnClickListener, в котором лямбда превратится в метод onClick(View v) .

Выводы

Язык программирования Kotlin с помощью своих «магических» функций существенно упрощает написание и, что не менее важно, чтение кода. А удобство и безопасность – наиболее важные отличия Kotlin от созданной в далёком 1995 году Java, когда о безопасности и удобстве мы только мечтали.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *