Kotlin руководство на русском

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

Это подборка базового синтаксиса с примерами.
В конце каждого раздела вы найдете ссылку на более подробное описание соответствующей темы.

Вы также можете изучить все основы Kotlin в бесплатном курсе Основы Kotlin от JetBrains Academy.

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

Имя пакета указывается в начале исходного файла, так же как и в Java.

package my.demo

import java.util.*

// ...

Но в отличие от Java, нет необходимости, чтобы структура пакетов совпадала со структурой папок:
исходные файлы могут располагаться в произвольном месте на диске.

См. Пакеты.

Точка входа в программу

В Kotlin точкой входа в программу является функция main.

fun main() {
    println("Hello world!")
}

Другая форма main может принимать массив строк String.

fun main(args: Array<String>) {
    println(args.contentToString())
}

Вывод в стандартный поток

print выводит свой аргумент в стандартный поток вывода.

print("Hello ")
print("world!")

println выводит свой аргумент и добавляет перевод строки, так что следующее, что вы выведите, появится на следующей строке.

println("Hello world!")
println(42)

Функции

Функция принимает два аргумента Int и возвращает Int.

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

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

fun sum(a: Int, b: Int) = a + b

Функция, не возвращающая никакого значения (void в Java).

fun printSum(a: Int, b: Int): Unit {
    println("сумма $a и $b равна ${a + b}")
}

Тип возвращаемого значения Unit может быть опущен.

fun printSum(a: Int, b: Int) {
    println("сумма $a и $b равна ${a + b}")
}

См. Функции.

Переменные

Неизменяемые (только для чтения) локальные переменные определяются с помощью ключевого слова val. Присвоить им значение можно только один раз.

val a: Int = 1   // Инициализация при объявлении
val b = 1        // Тип `Int` определен автоматически
val c: Int       // Указывать тип обязательно, если переменная не инициализирована сразу
c = 1            // Последующее присвоение

Изменяемые переменные объявляются с помощью ключевого слова var.

var x = 5 // Тип `Int` определен автоматически
x += 1

Вы можете объявлять глобальные переменные.

val PI = 3.14
var x = 0

fun incrementX() { 
    x += 1 
}

См. Свойства и поля.

Создание классов и экземпляров

Для создания класса используйте ключевое слово class.

class Shape

Свойства класса могут быть перечислены при его объявлении или в его теле.

class Rectangle(var height: Double, var length: Double) {
    var perimeter = (height + length) * 2 
}

Конструктор по умолчанию с параметрами, перечисленными при объявлении класса, доступен автоматически.

val rectangle = Rectangle(5.0, 2.0)
println("Периметр равен ${rectangle.perimeter}")

Чтобы объявить наследование между классами используйте двоеточие (:). По умолчанию классы являются финальными,
поэтому, чтобы сделать класс наследуемым, используйте open.

open class Shape

class Rectangle(var height: Double, var length: Double): Shape() {
    var perimeter = (height + length) * 2 
}

См. Классы и наследование и Объекты и экземпляры.

Комментарии

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

// Это однострочный комментарий

/* Это блочный комментарий
   из нескольких строк. */

Блочные комментарии в Kotlin могут быть вложенными.

/* Этот комментарий начинается здесь
/* содержит вложенный комментарий */
и заканчивается здесь. */

См. Документация Kotlin кода для информации о документации в комментариях.

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

Допустимо использование переменных внутри строк в формате $name или ${name}:

fun main(args: Array<String>) {
  if (args.size == 0) return

  print("Первый аргумент: ${args[0]}")
}
var a = 1
// просто имя переменной в шаблоне:
val s1 = "a равно $a" 

a = 2
// произвольное выражение в шаблоне:
val s2 = "${s1.replace("равно", "было равно")}, но теперь равно $a"

/*
  Результат работы программы:
  a было равно 1, но теперь равно 2
*/

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

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

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

В Kotlin if может быть использован как выражение (т. е. ifelse возвращает значение):

fun maxOf(a: Int, b: Int) = if (a > b) a else b

См. Выражение if.

Цикл for

val items = listOf("яблоко", "банан", "киви")
for (item in items) {
    println(item)
}

или

val items = listOf("яблоко", "банан", "киви")
for (index in items.indices) {
    println("${index} фрукт - это ${items[index]}")
}

См. Цикл for.

Цикл while

val items = listOf("яблоко", "банан", "киви")
var index = 0
while (index < items.size) {
    println("${index} фрукт - это ${items[index]}")
    index++
}

См. Цикл while.

Выражение when

fun describe(obj: Any): String =
    when (obj) {
        1          -> "Один"
        "Hello"    -> "Приветствие"
        is Long    -> "Long"
        !is String -> "Не строка"
        else       -> "Unknown"
    }

См. Выражение when.

Интервалы

Проверка на вхождение числа в интервал с помощью оператора in.

val x = 10
val y = 9
if (x in 1..y+1) {
    println("принадлежит диапазону")
}

Проверка значения на выход за пределы интервала.

val list = listOf("a", "b", "c")

if (-1 !in 0..list.lastIndex) {
    println("-1 не принадлежит диапазону")
}
if (list.size !in list.indices) {
    println("размер списка также выходит за допустимый диапазон индексов списка")
}

Перебор значений в заданном интервале.

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

Или по арифметической прогрессии.

for (x in 1..10 step 2) {
    print(x)
}
println()
for (x in 9 downTo 0 step 3) {
    print(x)
}

См. Интервалы.

Коллекции

Итерация по коллекции.

for (item in items) {
    println(item)
}

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

val items = setOf("яблоко", "банан", "киви")
when {
    "апельсин" in items -> println("сочно")
    "apple" in items -> println("яблоко тоже подойдет")
}

Использование лямбда-выражения для фильтрации и модификации коллекции.

val fruits = listOf("банан", "авокадо", "яблоко", "киви")
fruits
    .filter { it.startsWith("а") }
    .sortedBy { it }
    .map { it.uppercase() }
    .forEach { println(it) }

См. Коллекции.

Nullable-значения и проверка на null

Ссылка должна быть явно объявлена как nullable (символ ? в конце имени), когда она может принимать значение null.

Возвращает null, если str не содержит числа.

fun parseInt(str: String): Int? {
  // ...
}

Использование функции, возвращающей null.

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)
    
    // Использование `x * y` приведет к ошибке, потому что они могут содержать null
    if (x != null && y != null) {
        // x и y автоматически приведены к не-nullable после проверки на null
    print(x * y)
    }
    else {
        println("'$arg1' или '$arg2' не число")
    }
}

или

// ...
if (x == null) {
    print("Неверный формат числа arg1: '$arg1'")
    return
}
if (y == null) {
    print("Неверный формат числа arg2: '$arg2'")
    return
}

// x и y автоматически приведены к не-nullable после проверки на null
  print(x * y)

См. Null-безопасность.

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

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

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // в этом блоке `obj` автоматически преобразован в `String`
        return obj.length
    }

    // `obj` имеет тип `Any` вне блока проверки типа
    return null
}

или

fun getStringLength(obj: Any): Int? {
    if (obj !is String) return null

    // в этом блоке `obj` автоматически преобразован в `String`
    return obj.length
}

или даже

fun getStringLength(obj: Any): Int? {
    // `obj` автоматически преобразован в `String` справа от оператора `&&`
    if (obj is String && obj.length > 0) {
        return obj.length
    }

    return null
}

См. Классы и Приведение типов.

Последнее обновление: 23.07.2022

  1. Глава 1. Введение в язык Kotlin

    1. Что такое Kotlin. Первая программа

    2. Первая программа в IntelliJ IDEA

  2. Глава 2. Основы языка Kotlin

    1. Структура программы

    2. Переменные

    3. Типы данных

    4. Консольный ввод и вывод

    5. Операции с числами

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

    7. Условные конструкции

    8. Циклы

    9. Диапазоны

    10. Массивы

  3. Глава 3. Функциональное программирование

    1. Функции и их параметры

    2. Переменное количество параметров. Vararg

    3. Возвращение результата. Оператор return

    4. Однострочные и локальные функции

    5. Перегрузка функций

    6. Тип функции

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

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

    9. Лямбда-выражения

  4. Глава 4. Объектно-ориентированное программирование

    1. Классы и объекты

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

    3. Пакеты и импорт

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

    5. Модификаторы видимости

    6. Геттеры и сеттеры

    7. Переопределение методов и свойств

    8. Абстрактные классы и методы

    9. Интерфейсы

    10. Вложенные классы и интерфейсы

    11. Data-классы

    12. Перечисления enums

    13. Делегирование

    14. Анонимные классы и объекты

  5. Глава 5. Обобщения

    1. Обобщенные классы и функции

    2. Ограничения обобщений

    3. Вариантность, ковариантность и контравариантность

  6. Глава 6. Дополнительные возможности ООП

    1. Обработка исключений

    2. Null и nullable-типы

    3. Преобразование типов

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

    5. Перегрузка операторов

    6. Делегированные свойства

    7. Scope-функции

    8. Инфиксная нотация

  7. Глава 7. Коллекции и последовательности

    1. Изменяемые и неизменяемые коллекции

    2. List

    3. Set

    4. Map

    5. Последовательности

    6. Отличие последовательности от коллекций Iterable

    7. Фильтрация

    8. Проверка элементов

    9. Трансформации

    10. Группировка

    11. Сортировка

    12. Агрегатные операции

    13. Сложение, вычитание и объединение коллекций

    14. Получение части элементов

    15. Получение отдельных элементов

  8. Глава 8. Корутины

    1. Введение в корутины

    2. Область корутины

    3. launch и Job

    4. Async, await и Deferred

    5. Диспетчер корутины

    6. Отмена выполнения корутин

    7. Каналы

  9. Глава 9. Асинхронные потоки

    1. Введение в асинхронные потоки

    2. Создание асинхронного потока

    3. Операции с потоками

    4. Функции count, take и drop. Количество элементов в потоке

    5. Функции first, last, single

    6. Преобразование данных. Функции map и transform

    7. Фильтрация данных

    8. Сведение данных. Функции reduce и fold

    9. Объединение потоков

  • Глава 1. Введение в язык Kotlin
    • Что такое Kotlin. Первая программа
    • Первая программа в IntelliJ IDEA
  • Глава 2. Основы языка Kotlin
    • Структура программы
    • Переменные
    • Типы данных
    • Консольный ввод и вывод
    • Операции с числами
    • Условные выражения
    • Условные конструкции
    • Циклы
    • Диапазоны
    • Массивы
  • Глава 3. Функциональное программирование
    • Функции и их параметры
    • Переменное количество параметров. Vararg
    • Возвращение результата. Оператор return
    • Однострочные и локальные функции
    • Перегрузка функций
    • Тип функции
    • Функции высокого порядка
    • Анонимные функции
    • Лямбда-выражения
  • Глава 4. Объектно-ориентированное программирование
    • Классы и объекты
    • Конструкторы
    • Пакеты и импорт
    • Наследование
    • Модификаторы видимости
    • Геттеры и сеттеры
    • Переопределение методов и свойств
    • Абстрактные классы и методы
    • Интерфейсы
    • Вложенные классы и интерфейсы
    • Data-классы
    • Перечисления enums
    • Делегирование
    • Анонимные классы и объекты
  • Глава 5. Обобщения
    • Обобщенные классы и функции
    • Ограничения обобщений
    • Вариантность, ковариантность и контравариантность
  • Глава 6. Дополнительные возможности ООП
    • Обработка исключений
    • Null и nullable-типы
    • Преобразование типов
    • Функции расширения
    • Перегрузка операторов
    • Делегированные свойства
    • Scope-функции
    • Инфиксная нотация
  • Глава 7. Коллекции и последовательности
    • Изменяемые и неизменяемые коллекции
    • List
    • Set
    • Map
    • Последовательности
    • Отличие последовательности от коллекций Iterable
    • Фильтрация
    • Проверка элементов
    • Трансформации
    • Группировка
    • Сортировка
    • Агрегатные операции
    • Сложение, вычитание и объединение коллекций
    • Получение части элементов
    • Получение отдельных элементов
  • Глава 8. Корутины
    • Введение в корутины
    • Область корутины
    • launch и Job
    • Async, await и Deferred
    • Диспетчер корутины
    • Отмена выполнения корутин
    • Каналы
  • Глава 9. Асинхронные потоки
    • Введение в асинхронные потоки
    • Создание асинхронного потока
    • Операции с потоками
    • Функции count, take и drop. Количество элементов в потоке
    • Функции first, last, single
    • Преобразование данных. Функции map и transform
    • Фильтрация данных
    • Сведение данных. Функции reduce и fold
    • Объединение потоков

Основы Kotlin

Оглавление

  • Введение
  • Основные типы
    • Числа
    • Преобразования
    • Символы
    • Логический тип
    • Массивы
    • Строки
    • Строковые шаблоны
  • Пакеты
  • val и var
  • Управляющие конструкции
    • Условное выражение if
    • Условное выражение when
    • Цикл for
    • Цикл while
  • Классы
    • Конструкторы
    • Создание эксемпляров классов
    • Наследование
    • Переопределение членов класса
    • Абстрактные классы
    • Свойства и поля
    • Классы данных
  • Интерфейсы
  • Null-безопасность
    • Nullable типы и Non-Null типы
    • Проверка на null
    • Безопасные вызовы
    • Элвис-оператор
    • Оператор !!
  • Создать проект в IntelliJ IDEA

Введение

Документация на Kotlin: https://kotlinlang.org/docs/reference/basic-syntax.html.

Kotlin — это относительно молодой язык от российской компании JetBrains. Появился он в 2011 году.

Для нас он интересен тем, что на конференции Google I/O 2017 команда разработчиков Android сообщила, что Kotlin получил официальную поддержку для разработки Android-приложений. Сейчас Kotlin становится основным языком для разработки под Android. Именно его поддержке уделяется наибольшее внимание.

Основные возможности и преимущества Kotlin:

  • Компилируется в байт-код JVM, работает на виртуальной машине Java.
  • Программы могут использовать все существующие Java-фреймворки и библиотеки.
  • Kotlin можно интегрировать с Maven, Gradle и другими системами сборки.
  • Язык очень прост для изучения.
  • Исходный код открыт.
  • Легко читаемый лаконичный синтаксис.
  • В популярной среде разработки IntelliJ IDEA доступна автоматическая конвертация Java-кода в Kotlin и наоборот.

IntelliJ IDEA — среда разработки, созданная компанией JetBrains. Сейчас повсеместно используется для разработки Java- и Kotlin-приложений и не только.

Основные типы

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

Числа

Kotlin работает с численными типами так же как и Java.
Для представления чисел в Kotlin используются следующие встроенные типы:

  • Double — 64 бита.
  • Float — 32 бита.
  • Long — 64 бита.
  • Int — 32 бита.
  • Short — 16 бит.
  • Byte — 8 бит.

Числовые константы описываются следующим образом.
Десятичные числа: 123.
Long-тип обозначаются заглавной буквой L: 123L.
Шестнадцатеричные числа имеют приставку 0x: 0x0F.
Двоичные числа имеют приставку 0b: 0b00001011.

Чтобы сделать числовые константы более удобными к чтению, можно использовать нижние подчеркивания для разделения разрядов.
Например:

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

Преобразования

Меньшие типы являются подтипами больших типов.

// Возможный код, который на самом деле не скомпилируется:
val a: Int = 1 // "Int (java.lang.Integer)
val b: Long = a // Неявное преобразование возвращает Long (java.lang.Long)
println(a == b) // Данное выражение выведет "false" т.к. метод equals() типа Long предполагает, что вторая часть выражения также имеет тип Long

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

a.toLong() // Приведение Int к Long
// или
b.toInt() // Приведение Long к Int

Каждый численный тип поддерживает следующие преобразования:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double

При выполнении арифметических действий с разными типами выполняется преобразование результата к наибольшему типу.

val l = 1L + 3 // Long + Int => Long

Символы

Символы в Kotlin представлены типом Char. Они не являются числами, однако могут быть преобразованы к Int.

Символьные литералы записываются в одинарных кавычках: ‘1’, ‘n’, ‘uFF00’.
Мы можем явно привести символ в число типа Int:

val c = 'A'
val num: Int = c.toInt() // Явное преобразование в число
println(num)

Логический тип

Тип Boolean представляет логический тип данных и принимает два значения: true и false.

Встроенные действия над логическими переменными те же, что и в Java:

  • || – логическое ИЛИ,
  • && – логическое И,
  • ! — отрицание.

Массивы

Массивы в Kotlin представлены классом Array<T>. Массивы обладают функциями get и set (которые обозначаются [] согласно соглашению о перегрузке операторов), и свойством size, а также несколькими полезными встроенными функциями.

Для создания массива можно использовать библиотечную функцию arrayOf(), которой в качестве аргумента передаются элементы массива, т.е. выполнение arrayOf(1, 2, 3) создаёт массив [1, 2, 3]. Также существует библиотечная функция arrayOfNulls(). Она может быть использована для создания массива, заполненного значениями null.

val arr: Array<Int> = arrayOf(1, 2, 3)
println(arr.contentToString()) // Вывод содержимого массива в лог

Также в Kotlin есть особые классы для представления массивов примитивных типов без дополнительных затрат на оборачивание: ByteArray, ShortArray, IntArray, Boolean и т.д. Данные классы не наследуют класс Array, хотя и обладают тем же набором методов и свойств. У каждого из них есть соответствующая фабричная функция:

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2] // [5, 2, 3]

Строки

Строки в Kotlin представлены типом String. Строки являются неизменяемыми. Строки состоят из символов, которые могут быть получены с помощью операции индексирования: s[i].

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

Строки могут содержать шаблонные выражения, т.е. участки кода, которые выполняются, а полученный результат встраивается в строку. Шаблон начинается со знака доллара ($) и состоит либо из простого имени переменной, либо из произвольного выражения в фигурных скобках.

Примеры:

val i = 10
val s = "i = $i" // Строка "i = 10"
val s = "abc"
val str = "$s.length is ${s.length}" // Строка "abc.length is 3"

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

Пакеты

Файл с исходным кодом может начинаться с объявления пакета:

package org.example.kotlinlearn

fun someFunction() {}

class SomeClass {}

// ...

Всё содержимое файла с исходниками (например, классы и функции) располагается в объявленном пакете. Таким образом, в приведённом выше примере полное имя функции someFunction() будет org.example.kotlinlearn.someFunction, а полное имя класса SomeClassorg.example.kotlinlearn.SomeClass.

Каждый файл может содержать свои собственные объявления импорта.
Можно импортировать одно имя, например:

import org.example.kotlinlearn.SomeClass   
// теперь SomeClass можно использовать без указания пакета

или доступное содержимое пространства имён (пакет, класс, объект и т.д.):

import org.example.kotlinlearn.*
// всё в 'org.example.kotlinlearn' становится доступно без указания пакета

val и var

Переменные в Kotlin бывают изменяемые и неизменяемые.
Неизменяемые (только для чтения) переменные объявляются с помощью ключевого слова val:

val a: Int = 1
val b = 1   // Тип Int выведен автоматически
val c: Int  // Тип обязателен, когда значение не инициализируется
c = 1       // Последующее присвоение
c = 2       // Изменение невозможно, ошибка при компиляции

Изменяемые переменные объявляются с помощью ключевого слова var:

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

Управляющие конструкции

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

В языке Kotlin if является выражением, т.е. оно возвращает значение. Это позволяет отказаться от тернарного оператора (условие ? условие истинно : условие ложно), поскольку выражению if может его заменить.

// обычное использование 
var max = a 
if (a < b) 
  max = b 
 
// с блоком else 
var max: Int
if (a > b) 
  max = a 
else 
  max = b 
 
// в виде выражения 
val max = if (a > b) a else b

«Ветви» выражения if могут содержать несколько строк кода, при этом последнее выражение является значением блока и может быть возвращено:

val max = if (a > b) { 
    println("возвращаем a") 
    a 
} 
else { 
    println("возвращаем b") 
    b 
}
println(max) // Напечатает "возвращаем a", если a > b, и "возвращаем b", если a <= b.

Если конструкция if используется в качестве выражения (например, возвращая его значение или присваивая его переменной), то использование ветки else обязательно.

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

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

when (x) {
    1 -> println("x == 1")
    2 -> println("x == 2")
    else -> {
        println("x is neither 1 nor 2")
    }
}

Значение ветки else вычисляется в том случае, когда ни одно из условий в других ветках не удовлетворено.

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

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

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

when (x) {
    parseInt(s) -> println("s encodes x")
    else -> println("s does not encode x")
}

Также можно проверять вхождение аргумента в интервал in или !in или его наличие в коллекции:

when (x) {
    in 1..10 -> println("x is in the range")
    in validNumbers -> println("x is valid")
    !in 10..20 -> println("x is outside the range")
    else -> println("none of the above")
}

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

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

Цикл for

Цикл for обеспечивает перебор всех значений коллекции.

for (item in collection) {
    print(item)
}

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

for (i in array.indices) {
    print(array[i])
}

Также можно использовать библиотечную функцию withIndex() для получения индекса и значения по этому индексу одновременно:

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

Цикл while

Циклы while и do..while абсолютно аналогичны таковым же в Java:

// цикл while
while (x > 0) {
    x--
}

// цикл do..while
do {
    val y = retrieveData()
} while (y != null) // y здесь доступно!

Классы

Классы в Kotlin, как и в Java, объявляются с помощью ключевого слова class:

Тело класса является необязательным: если у класса нет тела, фигурные скобки могут быть опущены:

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

Класс в Kotlin может иметь основной конструктор (primary constructor) и один или более дополнительных конструкторов (secondary constructor). Основной конструктор является частью заголовка класса, его объявление идёт сразу после имени класса (и необязательных параметров):

class Customer constructor(name: String)

Ключевое слово constructor может быть опущено:

class Customer(name: String)

Основной конструктор не содержит в себе исполняемого кода. Код для инициализации класса принято помещать в соответствующий блок (initializers blocks), который помечается словом init. По сути блок init содержит код конструктора в привычном его виде. Параметры основного конструктора могут использоваться в инициализирующем блоке.

class Customer(name: String) {
    init {
        println("Customer initialized with value $name")
    }
}

В действительности, для объявления и инициализации свойств основного конструктора в Kotlin есть лаконичное синтаксическое решение:

class Person(val firstName: String, val lastName: String, var age: Int) {
  // Далее в теле класса свойства firstName, lastName и age доступны 
  // и проинициализированы значениями, переданными в конструктор класса Person
}

Для объявления дополнительных (secondary) конструкторов используется ключевое слово constructor:

class Customer(name: String) {

    constructor(name: String, cash: Int) : this(name) {
        println("Customer initialized with name $name and cash $cash")
    }
}

Здесь класс Customer имеет два конструктора: основной, принимающий на вход параметр name; и дополнительный, принимающий на вход параметры name и cash. Если класс имеет основной конструктор с параметром, то каждый его дополнительный конструктор должен делегировать вызов основному с передачей параметров. Именно для этого после сигнатуры конструктора после двоеточия указывается ключевое слово this с передачей параметров. Таким образом сначала будет вызван основной конструктор, а после него дополнительный.

Создание эксемпляров классов

Для создания экземпляра класса конструктор вызывается так, как если бы он был обычной функцией (без ключевого слова new, как в Java):

val customer = Customer("Joe Smith")

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

Для явного объявления суперкласса мы помещаем его имя за знаком двоеточия в объявлении класса:

open class Base(p: Int) {
    fun baseFunc() {}
}

class Derived(p: Int) : Base(p) {
    fun derivedFunc() {
        baseFunc()
    }
}

В данном примере класс Derived наследуется от класса Base и наследует его функцию baseFunc().

Ключевое слово open является противоположностью слову final в Java: оно позволяет другим классам наследоваться от данного. По умолчанию, все классы в Kotlin имеют статус final.

Если у класса нет основного (primary) конструктора, тогда каждый дополнительный (secondary) конструктор должен включать в себя инициализацию базового типа с помощью ключевого слова super. Примечательно, что любые дополнительные конструкторы могут ссылаться на разные конструкторы базового типа:

open class View(ctx: Context) {

  constructor(ctx: Context, attrs: AttributeSet) : this(ctx) {
    this.attrs = attrs
  }
}

class MyView : View {
    constructor(ctx: Context) : super(ctx) {
    }
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
    }
}

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

Kotlin требует указания аннотации и для методов класса, которые могут быть переопределены, и для самого переопределения:

open class Base {
    open fun v() {}
    fun nv() {}
}

class Derived() : Base() {
    override fun v() {}
    override fun nv() {} // Ошибка компиляции, т.к. nv() не помечен как open в классе Base
    open fun nov() {} // Пометить можно, но это не будет иметь эффекта, т.к. сам класс Derived не помечен как open
}

Для Derived.v() необходима аннотация override для переопределения метода. Если она отсутствует, компилятор выдаст ошибку. Если у функции типа Base.nv() нет аннотации open, переопределение этого метода в производном классе невозможно. В final классе (классе без аннотации open), запрещено использование аннотации open для его методов, как в случае с Derived.nov().

Член класса, помеченный override, является сам по себе open, т.е. он может быть переопределён в производных классах.

Абстрактные классы

Класс и некоторые его члены могут быть объявлены как abstract. Абстрактный член не имеет реализации в своём классе. Обратите внимание, что нам не надо аннотировать абстрактный класс или функцию словом open — это подразумевается и так.

abstract class Base(p: Int) {
    fun baseFunc() {}
    abstract fun abstractFunc();
}

class Derived(p: Int) : Base(p) {
    fun derivedFunc() {
        baseFunc()
    }

    override fun abstractFunc() {
        println("Base.abstractFunc()")
    }
}

Свойства и поля

Классы в Kotlin могут иметь свойства: изменяемые (mutable) и неизменяемые (read-only) — var и val соответственно.

public class Address {
    public var name: String = ...
    public var street: String = ...
    public var city: String = ...
}

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

fun copyAddress(address: Address): Address {
    val result = Address()
    result.name = address.name // вызов методов доступа
    result.street = address.street
    result.city = address.city
    return result
}

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

val stringRepresentation: String
    get() = "$name of $street in $city"
    set(value) {
        field = "$value new string"
    }

Классы в Kotlin не могут иметь полей. Т.е. переменные, которые объявляются внутри класса только выглядят и ведут себя как поля из Java, хотя на самом деле являются свойствами, для них неявно реализуются методы get() и set().

Классы данных

Нередко создаются классы лишь с целью хранения данных. Функционал таких классов зависит от самих данных, которые в них хранятся. В Kotlin класс может быть отмечен словом data и такой класс называют классом данных:

data class User(val name: String, val age: Int)

Компилятор автоматически формирует следующие методы для класса из свойств, объявленных в основном конструкторе:

  • пару функций equals()/hashCode() — для возможности сравнения конкретных объектов по значению и ссылке.
  • Функцию toString() в форме "User(name=John, age=42)" — для приведения объекта к строке,
  • Компонентные функции componentN(), которые соответствуют свойствам, в соответствии с порядком их объявления (name=component1(), age=component2()),
  • Функцию copy() — для создания копии объекта.

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

  • Основной конструктор должен иметь как минимум один параметр.
  • Все параметры основного конструктора должны быть отмечены, как val или var.
  • Классы данных не могут быть абстрактными или «открытыми» (open).

Интерфейсы

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

Интерфейс определяется ключевым словом interface:

interface MyInterface1 {
    fun interface1()
    fun method1() {
      // тело
    }
}

interface MyInterface2 {
    fun interface2()
    fun method2() {
      // тело
    }
}

Класс или объект могут реализовать любое количество интерфейсов:

class Child : MyInterface1, MyInterface2 {
    override fun interface1() {
        // тело
    }
    override fun interface2() {
        // тело
    }
}

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

interface MyInterface {
    val prop: Int // абстрактное свойство

    var _propWithImpl: String // абстрактное свойство
    var propWithImpl: String
        get() = _propWithImpl
        set(value) {
            _propWithImpl = value
        }

    fun interface2()
    fun method2() {
      // тело
    }
}

class Child : MyInterface {
    override val prop: Int = 29
    override val _propWithImpl: String = "property with implementation"
}

В рамках сеттеров интерфейса невозможно использовать ключевое слово field для доступа к значению свойства. Компилятор выдаст ошибку. Поэтому, если требуется переопределить сеттер свойства интерфейса, необходимо заводить еще одно дополнительное свойство, которое будет содержать значение свойства, сеттер которого мы переопределяем. Например, в приведенном выше примере выполняется переопределение геттера и сеттера свойства propWithImpl. Для этого заводится свойство _propWithImpl, которое используется в переопределяемых сеттере и геттере для получения и сохранения значения.

Null-безопасность

Nullable типы и Non-Null типы

Система типов в языке Kotlin нацелена на то, чтобы избавить программиста от опасности обращения к null-значениям.
Одним из самых распространённых подводных камней многих языков программирования, в том числе Java, является попытка произвести доступ к null-значению. Это приводит к ошибке. В Java такая ошибка называется NullPointerException.

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

  • Явное указание throw NullPointerException().
  • Использование оператора !! (описано ниже).
  • Эту ошибку вызвал внешний Java-код.
  • Есть какое-то несоответствие при инициализации данных (в конструкторе использована ссылка this на данные, которые не были ещё проинициализированы).

Система типов в Kotlin различает два типа ссылок: которые могут иметь значение null (nullable ссылки) и те, которые не могут иметь null-знаечния (non-null ссылки). Для того, чтобы разрешить null значение, необходимо добавить к объявлению типа переменноq знак вопроса ?, например, String?.

Пример:

var a: String = "abc"
a = null // ошибка компиляции, a — по умолчанию non-null
var l = a.length // ошибка компиляции, переменная a имеет значение null

var b: String? = "abc"
b = null // нет ошибки, переменная b может иметь значение null
var l = b.length // ошибка: переменная b имеет значение null

Пример выше показывает, что при вызове метода с использованием переменной a, получение NullPointerException исключено, т.к. компилятор не позволит собрать такой код, где a может иметь значение null.
Получить доступ к значению переменной b для вызова метода — небезопасно и компилятор предупредит об ошибке. Но если есть необходимость получить доступ к свойству, то есть несколько способов:

  • Проверка на null.
  • Безопасные вызовы.
  • Элвис-оператор (elvis operator или оператор ?:).
  • Оператор !!.

Проверка на null

Первый способ. Можно явно проверить переменную на null значение и обработать два варианта по отдельности:

val l = if (a != null) a.length else -1

Компилятор отслеживает информацию о проведённой проверке и позволяет вызывать length внутри блока if.

Безопасные вызовы

Вторым способом является оператор безопасного вызова ?.:

Этот код возвращает a.length в том, случае, если a не null. Иначе он возвращает null, и тогда типом этого выражения (переменной l) будет Int?.

Элвис-оператор

Он же elvis operator или оператор ?: (не путать с тернарным оператором).
Полезен в ситуации, когда необходимо либо вернуть некоторое значение сслылки, если она не null, либо некое значение «по-умолчанию».

Пример с if-else:

val l: Int = if (a != null) b.length else -1

Аналогом такому if-выражению является элвис-оператор ?::

Если выражение, стоящее слева от элвис-оператора, не является null, то элвис-оператор его вернёт (a.length). В противном случае в качестве возвращаемого значения послужит то, что стоит справа (-1).

Оператор !!

Для любителей NPE существует ещё один способ. Можно написать a!! и это вернёт либо non-null значение a, либо выкинет NPE:

Создать проект в IntelliJ IDEA

New -> Kotlin -> JVM -> Next.

Project Name -> Finish.

Add kotlin file -> Add configuration -> Run.

Предварительный просмотр в раннем доступе

  1. Настройте вашу сборку для EAP
  2. Примите участие в предварительном просмотре раннего доступа Kotlin
  3. Установите плагин EAP для IntelliJ IDEA или Android Studio
  4. Что нового в Котлин 1.7.20-RC

Урок 0: Введение в Kotlin, установка среды разработки IntelliJ IDEA

Настройка IntelliJ IDEA, установка JDK, первая программа Среда программирования Для установки и настройки среды программирования я использую IntelliJ IDEA от компании JetBrains. Установлю ее с помощью Toolbox App. Мне нравится работать через установщик, из-за его удобства. Можно устанавливать и обновлять…

Читать полностью

Урок 1: Переменные. Базовые типы данных. var, val и const val в Kotlin.

Что такое переменные? Если подойти к пониманию переменных абстрактно, можно сказать так, что то, что мы видим на экране компьютеров и смартфонов – в основном это данные в виде чисел и текста. То есть эта информация имеет числовой, строковый и…

Читать полностью

Создание файла в Котлин Классические операции, такие как сложение, вычитание, умножение, деление – в языке программирования Kotlin работают по тем же законам, что и в математике и используются точно такие же операторы. Перед тем, как начнем практиковаться, произведем небольшой рефакторинг…

Читать полностью

Урок 3: Строки в Kotlin. Интерполяция. Конкатенация.

Конкатенация строк Обсудим возможности строк в Котлине. Представим, что мы программируем бота, который будет приветствовать новых пользователей по имени и сообщать какую-нибудь информацию. Для этого распечатаем такую строку. Задача добавить к исходной строке приветствие. Самое простое, что можно сделать со…

Читать полностью

Урок 4: Boolean – логический тип данных в Kotlin. Операторы сравнения.

Эта информация призвана разложить по полочкам значения терминов, связанных с логическим типом данных. Конкретно рассмотрим: логические операторы, операторы сравнения и присваивания. Начнем с последнего. Раз и навсегда определим, что оператор присваивания, как вы уже знаете – обозначается знаком равно. Он…

Читать полностью

Урок 5: if else when в Kotlin Операторы ветвления или условные операторы.

Оператор if else Напомню гипотетический сценарий программы: при определенном пороге возраста нужно показывать страницу со скрытым контентом. Это ничто иное как условие. Мы говорим — “Если возраст больше или равен 18, то надо сделать какое-то действие”. Условиями напичкана любая программа,…

Читать полностью

Урок 6: Циклы в Kotlin. while, do-while.

Что такое циклы? Циклы позволяют многократно исполнять один и тот же код при определенных условиях. Во-первых, мы избегаем дублирования кода, что в принципе считается хорошим тоном. Во-вторых, количество итераций может быть бесконечно многим и повторять работу какого-либо компонента они должны,…

Читать полностью

Урок 7: Цикл for в Kotlin, диапазоны (интервалы).

Цикл for служит для перебора значений по каким-либо коллекциям, например, по спискам или по диапазонам чисел. В первой части расскажу про диапазоны (или интервалы), а затем перейдем к разбору конструкции for. Диапазоны (интервалы) Диапазоны – это такие объекты, которые содержат…

Читать полностью

Урок 8: Массивы в Kotlin. Функции для массивов.

Создание массивов разных типов Мы уже виртуозно умеем управляться с базовыми типами, такими как Int, String и так далее. Обычно мы создаем ряд переменных и работаем с ними. Представим, что мы делаем приложение с рецептами. И для яичницы есть 5…

Читать полностью

Урок 9: Списки в Kotlin, функции для работы с коллекциями.

Коллекции По определению коллекции – это группы с переменным количеством элементов (или нулем элементов). Объекты внутри как правило имеют единый тип. Коллекции называются коллекциями, потому что корнем их иерархии в языке является класс Collection<T>. Позже, при изучении наследования это станет…

Читать полностью

Урок 10: Функции в Kotlin. Как создать, получить и вернуть параметры.

Давайте поговорим о функциях в Kotlin. Что такое, как создавать, куда они возвращают результат и так далее. Перед нами пустой документ, который, как уже известно имеет главную функцию (или метод) main(). Название этой функции зарезервировано в языке, с нее начинается…

Читать полностью

Урок 11: ООП. Классы и объекты в Kotlin. Сущности и объекты.

Начинаем разговор про классы и объекты в языке программирования Kotlin. Давайте попробую объяснить сначала с точки зрения прикладного использования. Начнем с понятия классы. На протяжении всего обучения до этого момента мы оперировали переменными разных типов. Эти типы были как для…

Читать полностью

Урок 12: Конструкторы в Kotlin (primary, secondary). Блоки инициализации init.

Что такое конструктор в Kotlin Помните, как мы создавали экземпляр класса на прошлом уроке? Объявили переменную, затем инициализировали ее с помощью написания имени класса и его свойств в круглых скобках. Надо разобрать этот момент подробнее. В момент создания объекта, еще…

Читать полностью

Урок 13: Null Safety в Kotlin – операторы ?. !!, NullPointerException (NPE).

Что такое NPE Exceptions (или исключения) в программировании позволяют описать проблему, если в программе что-то пошло не так. Как правило это описание можно увидеть в логах, при возникновении ошибок. NullPointerException – это ошибка, которая возникает, когда используемый объект не инициализирован. Дословно она переводится…

Читать полностью

Урок 14: ООП. Наследование в Kotlin. open/super class, override.

Наследование считается одним из принципов ООП. Суть заключается в том, что можно создать один основной класс с базовыми свойствами и методами и отнаследовать от него более специализированные подклассы. Которые будут переиспользовать свойства и методы из основного класса, а также добавлять…

Читать полностью

Урок 15: Абстрактные классы и интерфейсы в Kotlin. Имплементация.

На прошлом уроке я вскользь упомянул ключевое слово abstract, с помощью которого мы запретили на время создавать экземпляры базового класса. То есть абстрактный класс предназначен по сути только для описания общих данных будущих экземпляров. От него также можно наследоваться, однако,…

Читать полностью

Урок 16: Модификаторы доступа (видимости) в Kotlin. public, private, protected, internal.

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

Читать полностью

Урок 17: Сеттеры и геттеры в Kotlin (setter, getter)

Из прошлого урока про модификаторы доступа плавно переходим к пониманию сеттеров и геттеров. Когда мы обращаемся к свойству какого-то класса, можно подумать, что мы делаем это напрямую. Однако, это не так. Kotlin под капотом генерирует так называемые сеттеры и геттеры.…

Читать полностью

Урок 18: ООП. Полиморфизм в Kotlin, 3 типа (Ad hoc, Subtyping, Parametric)

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

Читать полностью

Урок 19: ENUM в Kotlin (классы перечислений), TODO() и тип Nothing

Разбираем классы перечислений enum и выясняем чем они полезнее обычных строковых констант. Также затронем функцию TODO(), которая возвращает тип “ничего”. enum – это сокращение от enumeration. Перечисления с помощью enum классов используются, когда нужно перечислить какие-либо объекты. Такими объектами как правило…

Читать полностью

Понравилась статья? Поделить с друзьями:
  • Настольный светодиодный светильник lori tl90510 инструкция
  • Рабочие мануалы по заработку
  • Зарядное устройство general technologies gt bc006 инструкция
  • Экспедицией под руководством васко да гама был
  • Методическое руководство экономического отдела