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

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

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

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

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

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

var count: Int = 10

Int — это тип, представляющий целое число, один из многих числовых типов, которые могут быть представлены в Котлине. Как и в других языках, вы также можете использовать 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 по умолчанию не могут содержать нулевые значения. Это означает, что следующий фрагмент недействителен:

// Fails to compile
val languageName: String = null

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

val languageName: String? = null

Со String? type вы можете присвоить свойству languageName либо String значение, либо null .

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

Kotlin предоставляет ряд механизмов для безопасной работы с переменными, допускающими значение NULL. Дополнительные сведения см. в разделе Общие шаблоны Kotlin в Android: Nullability .

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

Котлин предлагает несколько механизмов реализации условной логики. Наиболее распространенным из них является оператор 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 в каждой ветке. Чтобы избежать такого повторения, Котлин предлагает условные выражения . Последний пример можно переписать следующим образом:

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 выражением if , как показано в следующем примере:

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

println(answerString)

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

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

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

Внутри условной ветви languageName может рассматриваться как не допускающее значения NULL. Kotlin достаточно умен, чтобы распознать, что условием выполнения ветки является то, что languageName не содержит нулевого значения, поэтому вам не нужно рассматривать languageName как допускающий значение NULL в этой ветке. Это умное приведение работает для проверок на 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 . Однако этот код не вызывает функцию. Чтобы получить результат функции, вы должны вызвать ее так же, как и именованную функцию. Вы должны указать String при вызове stringLengthFunc , как показано в следующем примере:

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 , а это означает, что доступ wheels можно получить за пределами класса Car , и его нельзя переназначить. Если вы хотите получить экземпляр 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.