Статьи Kotlin. Основной синтаксис
Post
Cancel

Kotlin. Основной синтаксис

В данной статье рассмотрим основной синтаксис языка Kotlin.

Переменные

Kotlin использует два разных ключевых слова для объявления переменных: val и var.

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

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

Обратите внимание, что переменная, объявленная ключевым словом val хранит ссылку на объект. Именно ссылку невозможно изменить после инициализации. А вот объект, на который она ссылается может быть изменяемым.

1
2
val languages = arrayListOf("Java")   // объявляется неизменяемая ссылка на объект
languages.add("Kotlin")               // изменение объекта, на который она указывает

При объявлении переменной сначала указывается ключевое слово, затем имя переменной и (в зависимости от ситуации) тип переменной.

В приведенном ниже примере count является переменной типа Int, которой присваивается начальное значение 10:

1
var count: Int = 10

Приведение типов

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

1
var languageName = "Kotlin"

Поскольку значение Kotlin имеет тип String, то компилятор разумно предположит, что и переменная languageName имеет тип String.

Тип определяется во время компиляции и далее никогда не меняется.

Защита от null

В некоторых языках переменные ссылочного типа могут объявляться без исходного явного значения. В этих случаях переменные обычно содержат нулевые значения. В Kotlin переменные не могут содержать нулевые значения по умолчанию. Это означает, что следующий код вызовет ошибку:

1
val languageName: String = null

Чтобы разрешить переменной хранить нулевое значение, нужно явно объявить ее как nullable при помощи символа ?. Тогда она сможет хранить как строку, так и null.

1
val languageName: String? = null

Проверка на null (if / else, безопасные вызовы)

Если переменная объявлена как nullable, Kotlin будет проверять ее и предупреждать о возможной ошибке на этапе разработки. Это означает, что перед выполнением какого-либо действия, нужно убедиться, что в переменной хранится не нулевое значение. Сделать это можно с помощью обычной проверки if / else, тем самым обработав оба варианта.

1
2
3
4
5
6
7
val a: Int? = 10

val b = if (a != null) {
  a*2
} else {
  -1
}

Такая проверка сработает только в том случае, если переменная не может быть изменена во время этой проверки (например, если переменная локальная или объявлена ключевым словом val). Иначе может оказаться так, что переменной присвоится значение null во время проверки.

Второй способ проверки - воспользоваться оператором безопасного вызова ?..

1
languageName?.length

В этом случае мы получим длину переменной languageName, если ее значение отлично от null. Иначе вернётся null.

Для проведения каких-либо операций исключительно над non-null значениями можно использовать оператор let вместе с оператором безопасного вызова:

1
 item?.let { println(it) }

Значение будет выведено на печать, если оно не null.


Условные выражения

if

Kotlin позволяет использовать if несколькими способами. Один из них ничем не отличается от применения if в Java:

1
2
3
4
5
if (x > 0) {
    println("x больше чем 0")
} else {
    println("x меньше чем 0")
}

Также if может выступать в качестве выражения, т.е. оно может вернуть значение и присвоить его переменной. В данном случае обязательным условием является использование else.

1
2
3
4
5
6
7
8
val max = if (a > b) {
    print("возвращаем a")
    a
  }
  else {
    print("возвращаем b")
    b
  }

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

По сути это тотже самый тернарный оператор в Java. Только в Kotlin от него отказались в пользу условных выражений.

when

По мере роста сложности условного выражения можно подумать о замене if-else на when. Ключевое слово when призвано заменить оператор switch, присутствующий в C-подобных языках. А его простейшая реализация может выглядеть так:

1
2
3
4
5
6
7
when (x) {
  1 -> print("x == 1")
  2 -> print("x == 2")
  else -> { // обратите внимание на блок, можно добавить несколько строк кода
    print("x is neither 1 nor 2")
  }
}

Каждая ветвь выражения when состоит из условия, стрелки -> и выполняемого действия (результата). Указанные значения последовательно сравниваются с предоставленным аргументом-условием пока не будет найдено совпадение. Если совпадения не найдено, то выполняется ветка else.

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

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

1
2
3
4
when (x) {
  0, 1 -> print("x == 0 or x == 1")
  else -> print("otherwise")
}

Помимо конкретных значений можно использовать произвольные выражения:

1
2
3
4
when (x) {
  parseInt(s) -> print("s encodes x")
  else -> print("s does not encode x")
}

И даже проверять, входит ли значение в заданный интервал и есть ли оно в коллекции:

1
2
3
4
5
6
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")
}

Циклы

Цикл for

Цикл for позволяет пройтись по всем элементам списка.

1
for (item in collection)

Из примера видно, что for имеет отличный от Java синтаксис: нет переменной цикла, изменяемой на каждом шаге, а также проверки выхода из цикла. Он чем-то напоминает forEach в Java.

В таком случае сразу возникает вопрос: как пройтись по определенному диапазону? Для этого нужно задать диапазон при помощи оператора ... При этом второе значение всегда является частью диапазона.

1
2
3
for(i in 1..5) {
  ...
}

Диапазон можно заменить переменной:

1
2
3
4
val range = 1..5
for(i in range) {
  ...
}

Если вам нужно узнать индекс позиции используйте функцию indices:

1
2
3
for (i in collection.indices) {
  ...
}

Если вам одновременно нужен и объект, и индекс - функцию withIndex:

1
2
3
for ((index, item) in collection.withIndex()) {
  ...
}

Чтобы перебрать объекты в обратном порядке используйте вместо оператора .. функцию downTo:

1
2
3
4
for(i in 5 downTo 1) {
  ...
}
// результат: 54321

При этом числа можно перебирать с произвольным шагом при помощи функции step.

1
2
3
4
5
6
7
8
9
10
for(i in 1..8 step 2) {
  ...
}
// результат: 1357


for (i in 8 downTo 1 step 2) {
  ...
}
// результат: 8642

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

1
2
3
4
for (i in 1 until 5) {       
    ...
}
// результат: 1234

Циклы while, do-while

Данные циклы работают также как и в Java.

Цикл while будет выполнять команды в своём блоке, пока условие остаётся истинным.

1
2
3
while (x > 0) {
  x--
}

Ну а do-while отличается от while тем, что сначала выполняет команды в блоке, а потом проверяет условие.

1
2
3
do {
    x++
} while (x < 10)

Цикл forEach

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

1
2
3
4
5
6
7
8
9
// цикл for
for (i in 1..5) {
  ...
}

// forEach
(1..5).forEach {
  ...
}

Делает он тоже самое - перебирает каждый элемент заданного массива или диапазона. Также его можно встраивать в цепочку вызовов.

1
listOf(1, 2, 3).filter( it == 2 ).forEach { println(it) }

Операторы перехода

С помощью операторов перехода можно завершить работу функции, либо цикла. Виды операторов:

  • return - позволяет выйти из текущей функции.
  • break - завершает выполнение цикла.
  • continue - продолжает выполнение цикла со следующего шага, без выполнения оставшегося кода в текущем шаге.

Метки

Любому выражению можно присвоить метку - label. Каждая метка состоит из идентификатора, за которым следует знак @.

1
2
3
loop@ for (i in 1..5) {
  ...
}

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

1
2
3
4
5
6
loop@ for (i in 1..5) {
  for (j in 1..5) {
    if (...)
      break@loop
  }
}

Если заменить оператор break на continue, то цикл с меткой loop продолжит работу со следующего шага.

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


Функции

Вместо того, чтобы повторять один и тот же ряд выражений везде, где требуется одинаковый результат, можно сгруппировать эти выражения в функцию и вызывать её. Объявляется функция с помощью ключевого слова fun, за которым следует её название, тип входных данных (если таковые имеются), тип возвращаемых данных (при необходимости) и тело функции.

Тело функции - это то место, где задаются выражения, которые выполняются при последующем вызове функции.

Входные данные (параметры) записываются в виде имя: тип и разделяются запятыми.

1
2
3
fun generate(x: Int, y: Int): Int {
	return x + y
}

При этом не обязательно объявлять функцию в теле класса. Т.е. можно создать файл, указать в нем название пакета и добавить в него нужные функции. Это удобно, когда есть методы, не относящиеся к реализации конкретного класса.

1
2
3
4
// файл NewClass.kt
package example

fun someFun(...): String {...}

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

Параметры функции могут иметь значения по умолчанию. Очень удобно использовать, если мы заранее знаем, что чаще всего у такого параметра будет одно и тоже значение. Если параметру задано значение по умолчанию, то при объявлении функции его можно не указывать. Запись будет иметь вид имя: тип = значение.

1
2
3
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) {
...
}

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

Именованные параметры

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

1
2
3
4
5
fun generate(first: Int, second: Int = 2, third: Int) {
    println("$first $second $third")
}

generate(1, 3) // не компилируется

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

1
generate(1, third = 3) // теперь должно заработать

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

Функции с единственным выражением

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

1
fun generate(x: Int, y: Int): Int = x + y

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

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

1
2
3
4
5
fun printNumbers(vararg integers: Int) {
    for (number in integers) {
        println("$number")
    }
}

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

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

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

Также такому параметру в качестве значения можно передать массив. Но для этого массив нужно пометить знаком spread *.

1
2
val a = arrayOf(1, 2, 3)
printNumbers(-1, 0, *a, 4)

Классы

Объявляются классы с помощью ключевого слова class. По умолчанию всем классам присваивается модификатор public, поэтому дополнительно его указывать не требуется.

1
2
class Person {
}

Конструкторы

В Kotlin может быть несколько конструкторов - основной и дополнительные. Основной является частью заголовка класса и объявляется сразу после имени класса.

1
class Person constructor(firstName: String)

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

1
class Person(firstName: String)

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

1
2
3
4
5
class Person(firstName: String) {
  init {
    print("Name - $firstName")
  }
}

Параметры основного конструктора могут быть использовать либо в блоке init (как показано выше), либо при инициализации свойств в теле класса.

1
2
3
class Person(firstName: String) {
  val key = firstName.toUpperCase()
}

Также есть еще один способ объявления и инициализации свойств основного конструктора - в самом конструкторе.

1
2
3
class Person(val firstName: String, val lastName: String, var age: Int) {
  ...
}

Дополнительные конструкторы всегда объявляются при помощи ключевого слова constructor в теле класса.

1
2
3
4
5
class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

Геттеры и сеттеры

При создании своего класса мы хотим сами управлять его свойствами, контролируя то, какие данные могут быть предоставлены или перезаписаны. С этой целью создаются get и set методы или иначе - геттеры и сеттеры. Цель get-метода - вернуть значение, а set-метода - записать полученное значение в свойство класса.

1
2
3
4
5
6
7
8
9
10
class Person(firstName: String) {

  val mame: String
      get() = newName

  var newName = firstName
      set(value) {
        field = value
      }
}

Если get и set методы не были созданы вручную, то для таких свойств Kotlin незаметно сам их генерирует. При этом для свойства, объявленного ключевым словом val, генерируется get-метод, а для свойства, объявленного ключевым свойством var - и get, и set методы.

Наследование

Родительский класс объявляется сразу после основного конструктора (если конструктора нет - после имени класса) - ставится знак двоеточия : и указывается имя родительского класса.

1
2
3
class Person(firstName: String) : BaseClass() {
  ...
}

Создание класса

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

1
val person = Person("Joe ")

Полезные ссылки

Официальная документация.
Неофициальный русский перевод документации.
Статьи от А.Климова про Kotlin.
Рекомендации Google по началу работы с Kotlin.

This post is licensed under CC BY 4.0 by the author.