August 26, 2019

Введение в Kotlin: функции, переменные, условия, циклы

Подготовка среды и решение простейшей задачи

Начать введение мы хотели бы с видео про то, как установить IntelliJ IDEA (также можно использовать Android Studio) и плагин Kotlin на ваш компьютер и решить простейшую задачу сложения двух чисел из файла и записи результата в другой файл. Видео располагается по ссылке.

Немного про магию среды IntelliJ IDEA

Как вы могли заметить в видео, набор кода происходит в некоторые моменты скачкообразно. Это совсем не эффект монтажа видео, а мощные функции среды IntelliJ IDEA:

  • Автодополнение. Чтобы набирать код быстрее, используйте сочетание Ctrl + Space после первых введенных символов.
  • Live templates. Позволяют разворачивать конструкции кода нажатием клавиши табуляции ⇥. Например, "main + tab" разворачивается в функцию main, "sout + tab" разворачивается в println().
  • Дублирование строки. Для того чтобы скопировать текущую строку в строку ниже, используйте сочетание клавиш Ctrl + D или ⌘ + D для Mac OS.
  • Инкрементальное выделение. Позволяет увеличивать зону выделения с каждым нажатием сочетания клавиш Ctrl + W или ⌘ + W для Mac OS от слова до всего файла на одну степень вложенности.

Теперь у вас установлен необходимый инструментарий, чтобы писать упражнения и оттачивать свое мастерство.

Определение функций

В общем случае у функций необходимо указывать возвращаемый тип:

fun sum(a: Int, b: Int): Int {
    return a + b
}

Однако, если возвращаемый тип может быть вычислен, то его указывать не обязательно:

//возвращаемый тип выводится автоматически  
fun sum(a: Int, b: Int) = a + b

В случае, если функция не возвращает значимый тип, то либо указывается Unit:

fun printSum(a: Int, b: Int): Unit {
    print(a + b)
}

либо ничего не указывается:

// возвращаемый тип писать необязательно, если подразумевается Unit
fun printSum(a: Int, b: Int) {
    print(a + b)
}

Смотрите про функции подробнее.

Определение локальной переменной

Локальные переменные подразделяются на 2 категории. Те, которые определяются лишь однажды (только для чтения), объявляются следующим образом:

val a: Int = 1
val b = 1 // Тип выводится автоматически
val c: Int // Тип нужно указывать явно, если инициализация происходит позже
c = 1

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

var x = 5 //Тип выводится автоматически
x += 1

Смотрите также Properties And Fields.

Строковые шаблоны

Вывод значений переменных в строке можно использовать с помощью конструкции "${variable}":

fun main(args: Array<String>) {
    if (args.size == 0) return
    print("First argument: ${args[0]}")
}

Также смотрите String templates и массивы.

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

Язык Kotlin позволяет писать условные выражения вида:

fun max(a: Int, b: Int): Int {
    if (a > b)
        return a
    else
        return b
}

и писать в более компактном представлении:

// 'if' - это выражение
fun max(a: Int, b: Int) = if (a > b) a else b

Подробнее про условные выражения и управляющие структуры.

Проверки на null

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

package multiplier

// Возвращает null, если строчка не содержит число
fun parseInt(str: String): Int? {
    // ...
}

fun main(args: Array<String>) {
    if (args.size < 2) {
        print("No number supplied");
    }

    val x = parseInt(args[0])
    val y = parseInt(args[1])

    // Нельзя написать 'x * y', потому что x и y могут быть null
    if (x != null && y != null) {
        print(x * y) // Теперь можно
    }
}

Kotlin запоминает факт проверки, если такая уже происходила:

// ...
if (x == null) {
    print("Wrong number format in '${args[0]}'")
    return
}
if (y == null) {
    print("Wrong number format in '${args[1]}'")
    return
}
print(x * y) // К этому моменту гарантируется, что x и y — не null

Смотри также Null-safety.

is-проверки и автоматическое приведение типов

Оператор is проверяет, является ли выражение экземпляром определенного типа. Если мы проверили с помощью оператора is неизменяемую переменную или свойство, то нет необходимости явно приводить ее к проверяемому с помощью явного приведения.

fun getStringLength(obj: Any): Int? {
    if (obj is String)
        return obj.length // явное приведение к String не нужно
    return null
}

или так:

fun getStringLength(obj: Any): Int? {
    if (obj !is String)
        return null
    return obj.length // явное приведение к String не нужно
}

Подробнее про классы и наследование и приведение типов.

Цикл for

fun main(args: Array<String>) {
    for (arg in args)
        print(arg)

    // или

    for (i in args.indices)
        print(args[i])
}

Подробнее про for-циклы.

Цикл while

fun main(args: Array<String>) {
    var i = 0
    while (i < args.size)
        print(args[i++])
}

Подробнее про while-цикл.

Оператор условия when

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

fun cases(obj: Any) {
    when (obj) {
        1 -> print("One")
        "Hello" -> print("Greeting")
        is Long -> print("Long")
        !is String -> print("Not a string")
        else -> print("Unknown")
    }
}

Генерация рядов и ключевое слово in

if (x in 1..y - 1)
    print("OK")

Также с помощью in можно проверить, что элемент не содержится в ряде:

if (x !in 0..array.lastIndex)
    print("Out")

Оператор in позволяет проверить принадлежность к коллекции.

if (obj in collection) // транслируется в вызов collection.contains(obj)
    print("Yes")

И наконец, с помощью in можно делать итеративный перебор элементов ряда:

for (x in 1..5)
    print(x)

Можно также итерироваться с произвольным шагом или в обратном направлении:

for (i in 1..4 step 2) print(i) // выводит "13"

for (i in 4 downTo 1) print(i) // выводит "4321"

for (i in 4 downTo 1 step 2) print(i) // выводит "42"

Подробнее.

Использование функциональных литералов для выполнения операций filter и map над коллекциями

val names = array("Eugene", "Ann", "Boris", "Alice", "Anton")
names.filter { it.startsWith("A") }
    .sortBy { it }
    .map { it.toUpperCase() }
    .forEach { print("${it} ") }

Подробнее про функции высших порядков.

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

fun String.hello() {
    println("Hello, $this!")
}

fun main(args: Array<String>) {
    "world".hello() // выводит 'Hello, world!'
}

В данном примере мы определили функцию-расширение hello() для типа String. Когда мы вызываем эту функцию на строке "world", мы используем значение строки в качестве аргумента ее функции hello().

Заключение

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

Источник: Введение в язык Kotlin