Kotlin
Полная грамматика языка: https://kotlinlang.org/docs/reference/grammar.html Полная документация: https://kotlinlang.org/docs/kotlin-docs.pdf
Определение функций
fun sum(a: Int, b: Int): Int {
return a + b
}
Если содержит не более одного выражения, то можно использовать сокращенную версию:
fun sum(a: Int, b: Int) = a + b
Если ничего не возвращает, то возвращаемый тип должен быть Unit
.
Function types
Тип-функция имеет такой синтаксис:
# (Int) -> String
val onClick: () -> Unit = ...
val a = { i: Int -> i + 1 }
Receiver type
Для типов-фукнций может быть указан тип-получатель:
A.(B) -> C
Это значит, что будет вызываться функция с заголовком (B) -> C
для инстанса типа A
. К инстансу в теле функции можно будет обратиться через this.
Пример:
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
Причем тип "функция с ресивером" взаимозаменяем с типом "функция без ресивера", если ресивер во втором случае передавать первым аргументом:
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
val twoParameters: (String, Int) -> String = repeatFun // OK
fun runTransformation(f: (String, Int) -> String): String {
return f("hello", 3)
}
val result = runTransformation(repeatFun) // OK
Лямбды
max(strings, { a, b -> a.length < b.length })
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
val sum = { x, y -> x + y }
Есть такое соглашение: если последний аргумент функции является функцией, то его можно вынести за скобки:
val product = items.fold(1) { acc, e -> acc * e }
Этот синтаксис называется trailing lambda.
it
Если у лямбды только 1 аргумент, то при объявлении лямбды список аргументов можно опустить и потом в теле обращаться к аргументу через it:
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
Возврат значения из лямбды
ints.filter {
val shouldFilter = it > 0
shouldFilter # автоматически возвращается результат последнего выражения
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter # если сделать просто return, то произойдет выход из функции, в которой объявляется эта лямбда
}
Интерполяция строк
println("i = $i")
println("Hello, ${args[0]}!") # для выражений нужно использовать фигурные скобки
Raw strings
val text = """
for (c in "foo")
print(c)
"""
Arrays
val a = arrayOf(1,2,3,4,5) # [1,2,3,4,5]
val b = arrayOfNulls(3) # [Null,Null,Null]
val c = Array(5) { i -> (i * i).toString() } # [0,1,4,9,16]
В последней строчке вызывается конструктор Array
, принимающий количество элементов и лямбду-инициализатор, принимающую в it
индекс элемента. Объявление такого конструктора выглядит так: <init>(size: Int, init: (Int) -> T)
Приведение типов
fun getStringLength(obj: Any): Int? {
if (obj is String)
return obj.length // no cast to String is needed
return null
}
Небезопасное
Если мы не хотим предварительно проверять тип через is
и уверены в себе, то можно сразу привести:
val x: String = y as String
Но следует помнить, что в случае несовместимости типов будет выброшено ClassCastException
. Если такой вариант не подходит, можно воспользоваться безопасным приведением.
Безопасное приведение
Обычное приведение типа может выбросить ClassCastException если типы несовместимы. Можно использовать безопасное приведение, которое в этом случае вернет null:
val aInt: Int? = a as? Int
For loop
val items = listOf("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
When
Аналог switch-case, только более умный, потому что проверяет не только на равенство, но и другие выражения:
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
Выражение when можно даже использовать как значение:
println(when (language) {
"EN" -> "Hello!"
"FR" -> "Salut!"
"IT" -> "Ciao!"
else -> "Sorry, I can't greet you in $language yet"
})
Выражение when
может быть хорошей заменой цепочке if-else. Еще примеры:
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
Можно даже захватывать субъект when (начиная с версии 1.3), пример:
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
Проверка на вхождение
val list = listOf("a", "b", "c")
if (2 in 0..list.lastIndex) {
println("2 is in range")
}
if (list.size !in list.indices) {
println("list size is out of valid list indices range, too")
}
Интервалы
for (x in 1..5) {
print(x)
}
for (x in 1..10 step 2) {
print(x)
}
for (x in 9 downTo 0 step 3) {
print(x)
}
Тернарный оператор
val language = if (args.size == 0) "EN" else args[0]
Классы
Конструкторы
У класса может быть первичный конструктор и несколько вторичных.
Первичный конструктор
Первичный конструктор является частью заголовка класса и не содержит кода:
class Person constructor(firstName: String) { /*...*/ } # вот здесь constructor(firstName: String) - это и есть первичный конструктор
Если нет никаких атрибутов и модификаторов видимости, то ключевое слово constructor
можно опустить:
class Person(firstName: String) { /*...*/ }
Если нужен какой-то код инициализации, то он должен быть помещен в initializer block, обозначаемый ключевым словом init
. Причем таких блоков может быть несколько и при инициализации инстанса они будут выполняться в порядке объявления:
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
В блоках инициализации и в инициализаторах пропертей могут быть использованы аргументы первичного конструктора:
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
Для объявления и инициализации полей есть специальный синтаксис:
class Customer(val name: String) { }
Эта запись аналогична:
class Custom(name: String) {
val name: String
init {
this.name = name
}
}
Вторичный конструктор
Вторичный конструктор обязательно должен иметь ключевое слово constructor
.
Если класс имеет первичный конструктор, то каждый вторичный должен ссылаться на него либо прямо, либо опосредованно через другой вторичный конструктор:
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
Null safety
var b: String? = "abc"
# val l = b.length // error: variable 'b' can be null
var l = if (b != null) b.length else -1 // OK
l = b?.length
Функция let
позволит выполнить non-null операцию над nullable типом:
b?.let { println(it) }
Elvis operator
Позволяет указать дефолтное значение для случая, когда выражение под вопросом равно null:
val l = b?.length ?: -1 # аналогично val l: Int = if (b != null) b.length else -1
Оператор !!
Превращает любое значение в non-null значение и бросает NUllPointerException если значение равно null.
val l = b!!.length
Data classes
Это классы, которые нужны исключительно для хранения данных.
Объявление:
data class User(val name: String, val age: Int)
Для таких классов компилятор автоматически генерирует реализации функций:
equals()/hashCode()
, если отсутствует явная пользовательская реализация;toString()
в форме"User(name=John, age=42)"
, если отсутствует явная пользовательская реализация;- функции
componentN()
, необходимые для мульти-деклараций https://kotlinlang.org/docs/reference/multi-declarations.html copy()
В сгенерированных функциях участвуют только аргументы первичного конструктора. Если должно быть поле, не участвующее в этих реализациях, то его нужно обновить внутри тела класса:
data class Person(val name: String) {
var age: Int = 0
}
Итерирование по словарю
fun main(args: Array<String>) {
val map = hashMapOf<String, Int>()
map.put("one", 1)
map.put("two", 2)
for ((key, value) in map) {
println("key = $key, value = $value")
}
}