Kotlin — современный статически типизированный язык программирования. А еще он выразительный и лаконичный: функционал Kotlin позволяет сократить код и сделать его удобным для человеческого восприятия. Посмотрим, как это происходит: перепишем код по-котлиновски.
Функции области видимости
Их цель — дать блоку кода контекст объекта. Таких функций пять: let
, run
, with
, apply
и also
.
Применение
let
: для вызова одной и более функций в результатах цепочек вызовов.
Without let
val basicSalary = getBasicSalary()
calculateHRA(basicSalary)
calculateDA(basicSalary)
calculateTA(basicSalary)
With let
getBasicSalary().let{
calculateHRA(it)
calculateDA(it)
calculateTA(it)
}
apply
: при обращении к нескольким свойствам объекта.
Without apply
val user = User()
user.name = "Simform"
user.email = "[email protected]"
With apply
val user = User().apply{
name = "Simform"
email = "[email protected]"
}
with
: для вызова нескольких методов в одном объекте.
Without
calculateHRA(basicSalary)
calculatePF(basicSalary)
Using with
with(basicSalary){
calculateHRA(this)
calculatePF(this)
}
run
: для инициализации объекта и вычисления возвращаемого значения.
val user = User().run{
name = "Simform"
formatAddress()
}
also
: для обращений к объекту, а не к его свойствам и функциям, или во избежание затенения обращения к this
из внешней области видимости.
val numbers = mutableListOf("one", "two", "three")
numbers.also {
println("The list elements before adding new one:$it")
}
.add("four")
Класс данных (POJO/POCO):
data class User(val name:String, val email:String)
По умолчанию дает такую функциональность:
1. Геттеры (и сеттеры в случае с var
переменными) для всех свойств.
2. equals()
.
3. hashCode()
.
4. toString()
.
5. copy()
.
6. component1()
, component2()
, …, для всех свойств (см. Data classes).
Параметры по умолчанию
Задают параметру функции значения по умолчанию. Если второй параметр не передается, функция принимает значение по умолчанию 0
. Либо использует передаваемое значение, если таковое имеется.
Так становится возможной перегрузка методов без необходимости писать несколько их сигнатур для разного количества аргументов.
fun add(var one: Int, var two: Int = 0)
Функции-расширения
Определяются для конкретных типов данных и вызываются аналогично функциям-членам с оператором (.)
.
Расширяют функциональность класса, создавая функции, которые вызываются только в объекте этого класса и определяются пользователем.
Позволяют добавлять больше функций в классы, к которым нет доступа, например встроенные классы (Int
, String
, ArrayList
и т. д.). В Kotlin много встроенных функций-расширений (toString()
, filter()
, map()
, toInt()
и т. д.).
fun Int.returnSquare(): Int { return this * this }
// Вызов функции-расширения
val number = 5
println("Square is ${number.returnSquare()}") // вывод: 25
Примечание: ключевое слово
this
указывает на объект получателя (в данном случаеInt
).
Оператор безопасного вызова (?.)
Применяется:
- В переменных типа, допускающего значение
null
, для безопасного их использования в коде и безnullPoiterException
.
val files = getFiles()
print(files?.size) // Выводит размер, когда файлы не «null»
2. Вместе с let
для вызова нескольких методов или выполнения операторов, когда переменная или выражение не null
.
val user = getUser()
user?.let{
// Выполняется, когда пользователь не «null»
saveUserToDB(it)
}
Оператор Элвиса (?:)
Without
if(user.name != null) {
userName = user.name
} else {
throw IllegalStateException()
}
With
val userName = user.name?: throw IllegalStateException()
run
: несколько операторов выполняется, когда значение null
.
user.name?.let{
// Выполняется, когда не «null»
} : run {
// Выполняется, когда «null»
}
Функции одного выражения
Просто возвращают значения:
fun isOdd(number:Int) = if(number%2 ==0) false else true
when
: похожа на switch
из Java, но более гибкая.
when(number:Int) {
5 -> "Greater than five"
in 6..10 -> "In range of 6 to 10"
else -> "This is else"
}
Оператор интервалов (..)
for( i in 1..10) // от 1 до 10
for( i in 1 until 10) // от 1 до 9 (10 не входит)
for( i in 1..10 step 2) // 1,3,5,7,9
for( i in 10 downTo 1) // 10,9,8....,1
Проверка экземпляра с помощью оператора (is)
if(10 is Int) // true
if(10 is Boolean) // false
if("string" !is Int) // true
Лямбда-функции
Это анонимные функции, рассматриваемые как значения. Передаются в качестве аргументов и сохраняются как переменные.
{argumentName: argumentType -> // тело лямбды}
Имя и тип аргумента обычно опускают. А вот тело лямбды указывается обязательно. Тип последней его строки — возвращаемый тип лямбды.
val double: (Int)-> Int = {number:Int -> number * number}
println(double(5)) // вывод: 25
Здесь лямбда принимает в качестве аргумента одно целое число и возвращает умножение с тем же числом.
Классные встроенные функции Kotlin
filter
: отфильтровывает list
, set
или map
с заданным предикатом и возвращает их с соответствующими ему элементами. Смотрите все варианты filter
здесь.
val numbers = listOf(1,2,3,4,5)
numbers.filter {element-> element%2 == 0 } // список вывода [2,4]
numbers.filterIndexed{index,element->(index != 0) && (element< 5)}
// [2,3,4]
numbers.filterNot {element-> element <= 3 } // [4,5]
map
: применяет заданный предикат или функцию преобразования к каждому элементу коллекции и возвращает новую коллекцию.
val numbers = listOf(1,2,3,4,5)
numbers.map { it * 3 } // список вывода [3,6,9,12,15]
numbers.mapIndexed { index,value -> value * index } // [0,2,6,12,20]
// следующие функции используются для получения значений, отличных от «null»
numbers.mapNotNull {value-> if ( value == 2) null else value * 3 }
// [3,9,12,15]
numbers.mapIndexedNotNull {
index, value -> if (index == 0) null else value * index
}
// [2,6,12,20]
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
numbersMap.mapKeys { it.key.uppercase() } //{KEY1=1,KEY2=2,KEY3=3,KEY11=11}
println(numbersMap.mapValues { it.value + it.key.length })
//{key1=5, key2=6, key3=7, key11=16}
zip
: создает список пар с элементами одного индекса из двух заданных списков.
val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")
println(colors.zip(animals))
// Вывод: [(red,fox),(brown,bear),(grey,wolf)]
unzip
создает из списка пар два списка:
val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
println(numberPairs.unzip())
// Вывод: ([one, two, three, four], [1, 2, 3, 4])
joinToString()
: создает строку со всеми добавленными элементами.joinTo()
: то же и добавляет строку к заданной строке в аргументе.
val numbers = listOf("one", "two", "three", "four")
// Вывод: one, two, three, four
val listString = StringBuffer("The list of numbers: ")
numbers.joinTo(listString)
// Вывод: список чисел: one, two, three, four
flatten()
: создает из списка списков один список.
val listOfList = [[1,2],[3,4,5],[6,7]]
println(listOfList.flatten()) // Вывод: [1,2,3,4,5,6,7]
any
: принимает лямбду и проверяет, соответствует ли заданный предикат в ней любому из элементов списка. Если да — возвращает true
, нет — тогда false
.all
: если заданный предикат соответствует всем элементам коллекции — возвращает true
, нет — тогда false
.none
: если заданный предикат не соответствует ни одному из элементов коллекции — возвращает true
, соответствует — тогда false
.
val numbers = listOf("one", "two", "three", "four")
numbers.any { it.endsWith("e") } // Вывод: true
numbers.none { it.endsWith("a") } // true
numbers.all { it.endsWith("e") } // false
partition
: возвращает пару списков (один с элементами, соответствующими условию, а другой — с несоответствующими).slice
: создает список с заданным индексом.chunked
: тоже создает список списков, но с заданным размером.
val numbers = listOf("one", "two", "three", "four")
numbers.partition { it.length > 3 }
// (["one","two"],["three","four"])
numbers.slice(1..3) // ["two","three","four"]
numbers.chuncked(2) // [["one","two"],["three", "four"]]
take
: получает указанное количество элементов, начиная с первого.takeLast
: то же, начиная с последнего.drop
: берет все элементы, кроме заданного количества первых элементов.dropLast
: то же, кроме заданного количества последних элементов. Больше методов см. здесь.
val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.take(3)) // [one, two, three]
println(numbers.takeLast(3)) //[four, five, six]
println(numbers.drop(1)) // [two, three, four, five, six]
println(numbers.dropLast(5)) // [one]
groupBy
: принимает лямбда-функцию и возвращает карту. В конечной карте ключи будут результатом лямбда-функций, а значения — элементом соответствующего списка, к которому применяется лямбда-функция. Объединяет элементы списка с конкретными условиями.
val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.groupBy { it.first().uppercase() })
// Вывод: {O=[one], T=[two, three], F=[four, five]}
average
: возвращает среднее всех элементов списка.sum
: возвращает их сумму.count
: возвращает их количество.minOrNull
: возвращает минимальное значение списка, а в случае пустого списка — значение NULL
.maxOrNull
: то же самое, но вместо минимального значения возвращается максимальное.
val numbers = listOf(6, 42, 10, 4)
println("Count: ${numbers.count()}") // Вывод: 4
println("Max: ${numbers.maxOrNull()}") //42
println("Min: ${numbers.minOrNull()}") //4
println("Average: ${numbers.average()}")//15.5
println("Sum: ${numbers.sum()}") //62
Есть также функции, относящиеся к коллекциям:
1. Спискам.
2. Множествам.
3. Картам.
Функции для избежания ошибки indexOutOfBound
elementAtOrNull()
: возвращает значение null
, когда указанная позиция за пределами коллекции.elementAtOrElse()
: возвращает результат лямбды в заданном значении, когда указанная позиция за пределами коллекции.
Функции для избежания ошибки numberFormatException
toIntOrNull()
: преобразует строку в целое число и возвращает null
, когда возникает исключение.toDoubleOrNull()
: то же, но преобразует в число двойной точности.toFloatOrNull()
: то же, но преобразует в число с плавающей запятой.
Примечание: во избежание появления
NullPointerException
(исключения нулевого указателя) нужно самостоятельно обрабатывать значенияnull
.
Заключение
Это был краткий обзор функционала Kotlin, и кое-что в него не вошло. Другие крутые функции здесь.
Читайте также:
- Kotlin 1.5.30 и KMM/KMP
- С Kotlin приведение стало еще удобнее
- Ключевое слово vararg и оператор spread в Kotlin
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Abhishek Ippakayal: Kotlin Tips and Tricks for Efficient Programming