Изучите язык программирования Kotlin

Kotlin – это язык программирования, который широко используется разработчиками Android. В этой статье вы найдете краткий курс по Kotlin, который поможет вам быстро начать работу.

Объявление переменной

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

  • Используйте val для переменной, значение которой никогда не меняется. Нельзя переназначить значение переменной, объявленной с помощью val.
  • Используйте var для переменной, значение которой может меняться.

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

var count: Int = 10

Int – это тип, представляющий целое число, один из многих числовых типов, которые можно представить в Kotlin. Как и в других языках, в русском можно использовать Byte, Short, Long, Float и Double в зависимости от того, какие числовые данные вы хотите представить.

Ключевое слово var означает, что при необходимости вы можете переназначить значения для count. Например, можно изменить значение count с 10 на 15:

var count: Int = 10
count = 15

Однако некоторые значения менять нельзя. Рассмотрим String под названием languageName. Если вы хотите, чтобы переменная languageName всегда содержала значение "Kotlin", объявите ее languageName с помощью ключевого слова val:

val languageName: String = "Kotlin"

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

Вывод типов

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

Поскольку значение "Kotlin" имеет тип String, компилятор делает вывод, что languageName также относится к типу String. Обратите внимание, что Kotlin – это статически типизированный язык. Это означает, что тип определяется во время компиляции и никогда не меняется.

В следующем примере languageName определяется как String, поэтому вы не можете вызывать функции, которые не являются частью класса String:

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() – это функция, которую можно вызывать только для переменных типа String. Поскольку компилятор Kotlin определил languageName как String, вы можете безопасно вызвать toUpperCase(). Однако inc() – это оператор Int, поэтому его нельзя вызвать для String. Подход Kotlin к выводу типов обеспечивает как краткость, так и безопасность типов.

Безопасность при работе с нулевыми значениями

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

// Fails to compile
val languageName: String = null

Чтобы переменная могла содержать нулевое значение, она должна быть допускающей нулевое значение. Чтобы указать, что переменная может иметь значение null, добавьте к ее типу суффикс ?, как показано в следующем примере:

val languageName: String? = null

Для типа String? можно назначить значение String или null для languageName.

С переменными, допускающими значение NULL, нужно обращаться осторожно, иначе вы рискуете получить ошибку NullPointerException. Например, в Java при попытке вызвать метод для нулевого значения программа завершает работу со сбоем.

В Kotlin есть несколько механизмов, позволяющих безопасно работать с переменными, допускающими значение null. Подробнее о распространенных шаблонах Kotlin в Android: допустимость значения NULL…

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

В Kotlin есть несколько механизмов для реализации условной логики. Самый распространенный из них – оператор if-else. Если выражение в скобках рядом с ключевым словом if имеет значение true, то выполняется код в этой ветви (т.е. код, следующий сразу за выражением и заключенный в фигурные скобки). В противном случае выполняется код в ветви else.

if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

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

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

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

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

Каждая ветвь условного оператора неявно возвращает результат выражения в последней строке, поэтому ключевое слово return использовать не нужно. Поскольку результат всех трех ветвей имеет тип String, результат выражения if-else также имеет тип String. В этом примере переменной answerString присваивается начальное значение из результата выражения if-else. Вывод типов можно использовать, чтобы опустить явное объявление типа для answerString, но часто лучше включить его для ясности.

Если условное выражение становится слишком сложным, вы можете заменить выражение if-else на выражение when, как показано в следующем примере:

val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

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

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

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

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

Функции

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

Чтобы объявить функцию, используйте ключевое слово fun, за которым следует название функции. Затем укажите типы входных данных, если они есть, и тип выходных данных. В теле функции определяются выражения, которые вызываются при ее вызове.

Ниже приведен пример полной функции Kotlin, основанный на предыдущих примерах:

fun generateAnswerString(): String {
    val answerString = if (count == 42) {
        "I have the answer."
    } else {
        "The answer eludes me"
    }

    return answerString
}

В примере выше функция называется generateAnswerString. Он не принимает никаких входных данных. Он возвращает результат типа String. Чтобы вызвать функцию, используйте ее имя, за которым следует оператор вызова (()). В приведенном ниже примере переменная answerString инициализируется результатом функции generateAnswerString().

val answerString = generateAnswerString()

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

fun generateAnswerString(countThreshold: Int): String {
    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }

    return answerString
}

При объявлении функции можно указать любое количество аргументов и их типы. В примере выше функция generateAnswerString() принимает один аргумент countThreshold типа Int. Внутри функции можно ссылаться на аргумент, используя его название.

При вызове этой функции необходимо указать аргумент в скобках:

val answerString = generateAnswerString(42)

Упрощение объявлений функций

generateAnswerString() – довольно простая функция. Функция объявляет переменную, а затем сразу же возвращает значение. Если функция возвращает результат одного выражения, можно не объявлять локальную переменную, а сразу возвращать результат выражения if-else, как показано в следующем примере:

fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }
}

Вы также можете заменить ключевое слово return оператором присваивания:

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
        "I have the answer"
    } else {
        "The answer eludes me"
    }

Анонимные функции

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

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

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

В приведенном выше примере функция stringLengthFunc содержит ссылку на анонимную функцию, которая принимает String в качестве входных данных и возвращает длину входных данных String в качестве выходных данных типа Int. Поэтому тип функции обозначается как (String) -> Int. Однако этот код не вызывает функцию. Чтобы получить результат функции, ее нужно вызвать так же, как и именованную функцию. При вызове stringLengthFunc необходимо указать String, как показано в следующем примере:

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

val stringLength: Int = stringLengthFunc("Android")

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

Функция может принимать другую функцию в качестве аргумента. Функции, которые используют другие функции в качестве аргументов, называются функциями высшего порядка. Этот шаблон полезен для взаимодействия между компонентами, как и интерфейс обратного вызова в Java.

Вот пример функции высшего порядка:

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

Функция stringMapper() принимает объект String и функцию, которая извлекает значение Int из объекта String, переданного в функцию.

Вы можете вызвать функцию stringMapper(), передав ей параметр String и функцию, которая соответствует другому входному параметру, а именно функцию, которая принимает параметр String и возвращает параметр Int, как показано в следующем примере:

stringMapper("Android", { input ->
    input.length
})

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

stringMapper("Android") { input ->
    input.length
}

Анонимные функции можно найти во всей стандартной библиотеке Kotlin. Подробнее о функциях высшего порядка и лямбда-функциях…

Курсы

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

class Car

Свойства

Классы представляют состояние с помощью свойств. Свойство – это переменная уровня класса, которая может включать метод получения, метод установки и резервное поле. Поскольку автомобилю нужны колеса, чтобы ездить, вы можете добавить список объектов Wheel в качестве свойства объекта Car, как показано в следующем примере:

class Car {
    val wheels = listOf<Wheel>()
}

Обратите внимание, что wheels – это public val, то есть к нему можно получить доступ извне класса Car, и его нельзя переназначить.wheels Чтобы получить экземпляр Car, сначала нужно вызвать его конструктор. После этого вы сможете получить доступ к любому из доступных ресурсов.

val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

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

class Car(val wheels: List<Wheel>)

В примере выше конструктор класса принимает List<Wheel> в качестве аргумента и использует его для инициализации свойства wheels.

Функции класса и инкапсуляция

Классы используют функции для моделирования поведения. Функции могут изменять состояние, помогая вам показывать только те данные, которые вы хотите. Этот контроль доступа является частью более крупной объектно-ориентированной концепции, известной как инкапсуляция.

В следующем примере свойство doorLock является частным для всего, что находится за пределами класса Car. Чтобы разблокировать автомобиль, вызовите функцию unlockDoor(), передав ей действительный ключ, как показано в следующем примере:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

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

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

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

Совместимость

Одна из самых важных особенностей Kotlin – возможность беспрепятственно использовать его вместе с Java. Поскольку код Kotlin компилируется в байт-код JVM, код Kotlin может напрямую вызывать код Java и наоборот. Это означает, что вы можете использовать существующие библиотеки Java непосредственно из Kotlin. Кроме того, большинство API Android написаны на Java, и вы можете вызывать их напрямую из Kotlin.

Дальнейшие действия

Kotlin – это гибкий и практичный язык, который становится все более популярным. Если вы ещё не пробовали, рекомендуем сделать это. Чтобы узнать больше, ознакомьтесь с официальной документацией по Kotlin и руководством по применению распространенных шаблонов Kotlin в приложениях Android.