Викиучебник:Объектно-ориентированное программирование в Kotlin

Материал из Викиучебника — открытых книг для открытого мира

О Kotlin[править]

Язык программирования Kotlin начал разрабатываться в петербургской компании JetBrains в 2010 году. Официальный релиз продукта был выпущен в 2016 году.
Такое название язык получил в честь острова в Финском заливе, на котором расположен Кронштадт.
По интересному совпадению, название популярного языка Java – это тоже имя острова в Индонезии. Как сообщается в пресс-релизе, Kotlin должен работать везде, где работает Java, и один из ориентиров был сделать такой продукт, который можно будет использовать в смешанных проектах, которые создаются на нескольких языках.

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

Kotlin — современный статически типизированный объектно-ориентированный язык программирования, компилируемый для платформ Java и JavaScript. При полной совместимости с Java, Kotlin предоставляет дополнительные возможности, упрощающие повседневную работу программиста и повышающие продуктивность. Kotlin сочетает в себе лаконичность, выразительность, производительность и простоту в изучении.

Достоинства языка программирования Kotlin[править]

К основным достоинствам языка Kotlin относят:

  • кроссплатформенность (можно создавать приложения на платформах Windows, Linux, Mac OS, iOS, Android);
  • бесплатность;
  • выразительность;
  • масштабируемость;
  • взаимодействие (Kotlin полностью совместим со всеми основанными на Java фреймворками);
  • лаконичность (код на Kotlin примерно на 40% короче, чем код на Java);
  • миграция (Kotlin поддерживает постепенную миграцию больших кодовых баз с Java на Kotlin).

Самым популярным направлением, где применяется Kotlin, является, прежде всего, разработка под ОС Android.

Kotlin подходит для разработки серверных приложений, позволяя писать сжатый и выразительный код, сохраняя при этом полную совместимость с существующими стеками технологий Java, также подходит для web-разработки и разработки Desktop-приложений.

Kotlin поддерживается всеми крупными IDE для Java, включая IntelliJ IDEA, Android Studio, Eclipse и NetBeans.

Полезные ресурсы[править]

Для более детального знакомства с языком программирования Kotlin можно обратиться на официальные сайты компании:

Основы языка программирования Kotlin[править]

Переменные. Типы данных.[править]


Для хранения данных в программе в Kotlin применяются переменные. Каждая переменная характеризуется определенным именем, типом данных и значением.

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

Для определения переменной можно использовать ключевое слово val (immutable variable) – для определения неизменяемой переменной, либо var (mutable variable) – значение такой переменной можно многократно изменять в ходе программы. При объявлении переменной вначале идет ключевое слово, затем имя переменной и через двоеточие тип переменной.

   val age: Int 

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

   val age: Int 
age = 23

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

Присвоение значения переменной должно производиться только после ее объявления. И также мы можем сразу присвоить переменной начальное значение при ее объявлении. Такой прием называется инициализацией:

   val age: Int = 23 

В Kotlin каждая переменная имеет определенный тип. Тип данных определяет, какие операции можно производить с данными этого типа.
Числовые типы:

Тип Описание
Byte хранит целое число от -128 до 127 и занимает 1 байт
Short хранит целое число от -32768 до 32767 и занимает 2 байта
Int хранит целое число от -2147483648 до 2147483647 и занимает 4 байта
Long хранит целое число от –9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 и занимает 8 байт
Float хранит число с плавающей точкой от -3.4*1038 до 3.4*1038 и занимает 4 байта
Double хранит число с плавающей точкой от ±5.0*10-324 до ±1.7*10308 и занимает 8 байта

Литералы
Литералы представляют неизменяемые значения (иногда их еще называют константами). Литералы можно передавать переменным в качестве значения. Литералы бывают логическими, целочисленными, вещественными, символьными и строчными.
Любые литералы, которые представляют целые числа, воспринимаются как данные типа Int:

   val age: Int = 45

Если же необходимо явно указать, что число представляет значение типа Long, то следует использовать суффикс L:

   val age: Long = 45L

Аналогично все числа с плавающей точкой (которые содержат точку в качестве разделителя целой и дробной части) рассматриваются как числа типа Double:

   val weight: Double = 68.71

Если ytj,[jlbvj указать, что данные будут представлять тип Float, то необходимо использовать суффикс F:

   val weight: Float = 68.71F

Кроме чисел в десятичной системе мы можем определять числа в двоичной и шестнадцатеричной системах. Шестнадцатеричная запись числа начинается с 0x, затем идет набор символов от 0 до F, которые представляют число:

   val age: Int = 0x0A1    // 161

Двоичная запись числа начинается символами 0b, после которых идет последовательность из нулей и единиц:

   val a: Int = 0b0101    // 5
val b: Int = 0b1011 // 11

Логический тип Boolean.
Тип Boolean может хранить одно из двух значений: true (истина) или false (ложь).

   val a: Boolean = true
val b: Boolean = false

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

   val a: Char = 'A' 
val b: Char = 'B'
val c: Char = 'T'

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

  • \t: табуляция
  • \n: перевод строки
  • \r: возврат каретки
  • \': одинарная кавычка
  • \": двойная кавычка
  • \\: обратный слеш

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

   val name: String = "Hello" 

Строка может содержать специальные символы или эскейп-последовательности. Например, если необходимо вставить в текст перевод на другую строку, можно использовать эскейп-последовательность \n:

   val text: String = "Hello \n World!" 

Шаблоны строк
Шаблоны строк (string templates) представляют удобный способ вставки в строку различных значений, в частности, значений переменных. Так, с помощью знака доллара $ можно вводить в строку значения различных переменных.

   val firstName = "Tom" 
val lastName = "Smith"
val welcome = "Hello, $firstName $lastName"
println(welcome) // Hello, Tom Smith

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

   val name = "Tom" 
val age = 22
val userInfo = "Your name: $name Your age: $age"

Тип Any
Тип Any является базовым для всех остальных типов. Остальные базовые типы, такие как Int или Double, являются производными от Any. Соответственно можно присвоить переменной данного типа любое значение.

   var name: Any = "Tom Smith" 
name = 6758

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

   val age = 5 
val name = "Tom"

В первом случае переменная будет иметь тип Int, во втором - String.
При этом обязательно надо инициализировать переменную некоторым значением. Нельзя сначала объявить переменную, а только потом присвоить ей какое-то значение.

Операции с числами. Условные выражения[править]

Kotlin поддерживает базовые арифметические операции:

  1. + (сложение).
  2. – (вычитание).
  3. * (умножение).
  4. / (деление).
  5. % (остаток от целочисленного деления).
  6. ++(инкремент) – увеличивает значение на 1 (префиксный: val a = ++a, постфиксный: val a = a++)
  7. –(декремент) – уменьшает значение на 1 ( префиксный: val a = --a, постфиксный: val a = a--)


В Kotlin существует ряд операций присваивания:

  1. += (присваивание после сложения). Эквивалентно записи: a+=b => a=a+b.
  2. -= (присваивание после вычитания). Эквивалентно записи: a-=b => a=a-b.
  3. *= (присваивание после умножения). Эквивалентно записи: a*=b => a=a*b.
  4. /= (присваивание после деления). Эквивалентно записи: a/=b => a=a/b.
  5. %= (присваивание после деления по модулю). Эквивалентно записи: a%=b => a=a%b.


Условные выражения представляют некоторое условие, которое возвращает значение типа Boolean: либо true (если условие истинно), либо false (если условие ложно).

Операции отношения:

  • > (больше, чем) - возвращает true, если первый операнд больше второго, иначе возвращает false;
  • < (меньше, чем) - возвращает true, если первый операнд меньше второго, иначе возвращает false;
  • >= (больше или равно) - возвращает true, если первый операнд больше или равен второму;
  • <= (меньше или равно) - возвращает true, если первый операнд меньше или равен второму;
  • == (равно) - возвращает true, если оба операнда равны, иначе false;
  • != (не равно) - возвращает true, если оба операнда не равны, иначе false.

Поток ввода-вывода данных. Преобразование типов.[править]

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

Математические функции в Kotlin объединены как совокупность методов в классе math, который находится в пакете kotlin.math.*.

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

  1. Класс Scanner используется для ввода данных различного типа. Данный класс импортируется из стандартной библиотеки Java.
  2. Функция readLine() приводит полученные данные к строковому типу.


Встроенные методы преобразования типов

Чтобы использовать данные одного типа в контексте, где используется другой тип, в Kotlin существует возможность преобразования типов.
Для преобразования данных одного типа в другой можно использовать встроенные следующие функции, которые есть у базовых типов (Int, Long, Double и т.д.):

  • toByte
  • toShort
  • toInt
  • toLong
  • toFloat
  • toDouble
  • toChar

Все эти функции преобразуют данные в тот тип, которые идет после префикса to.

Логические операции. Условные конструкции.[править]


Для работы с операндами типа Boolean в Kotlin используются логические операции. Логические операции объединяют несколько операций отношения:

  • and - возвращает true, если оба операнда равны true;
  • or - возвращает true, если хотя бы один из операндов равен true;
  • xor - возвращает true, если только один из операндов равен true если равны оба возвращается false;
  • !: ( not() ) - возвращает true, если операнд равен false, наоборот, если операнд равен false, возвращает true.
  • in - возвращает true, если операнд имеется в некоторой последовательности.


Условные конструкции
Условные конструкции позволяют направить выполнение программы по одному из путей в зависимости от условия.

  1. if...else. Конструкция if принимает условие, и если это условие истинно, то выполняется последующий блок инструкций. Если условное выражение после оператора if истинно, то выполняется блок после if, если ложно - выполняется блок после else.
  2. when. Проверяет значение некоторого объекта и в зависимости от его значения выполняет тот или иной код.

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

Последовательности. Циклы.[править]


Последовательность представляет набор значений или диапазон. Для создания последовательности применяется оператор «…».

   * var range = 1..5

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

   * var range =  "a".."d"

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

Функции:

  • Функция downTo можно построить последовательность в обратном порядке.
   var range2 =  5 downTo 1
  • Функция step позволяет задать шаг, на который будут изменяться последующие элементы.
   var range2 = 10 downTo 1 step 3
  • Функция until позволяет не включать верхнюю границу в саму последовательность.
   var range2 = 1 until 9 step 2

С помощью специальных операторов можно проверить наличие или отсутствие элементов в последовательности:

  • in: возвращает true, если объект имеется в последовательности
  • !in: возвращает true, если объект отсутствует в последовательности


Пример:

   var range = 1..5
var isInRange = 5 in range
println(isInRange)
   isInRange = 86 in range
   println(isInRange)
   var isNotInRange = 6 !in range
println(isNotInRange)
isNotInRange = 3 !in range
println(isNotInRange)

Циклы.

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

1. Цикл for. Цикл пробегается по всем элементам коллекции. Его формальная форма выглядит следующим образом:

   for(переменная in последовательность){
выполняемые инструкции
}

2. Цикл while. Цикл повторяет определенные действия пока истинно некоторое условие:

   var i = 10
while(i > 0){
println(i*i)
i--;
}

Пока переменная i больше 0, будет выполняться цикл, в котором на консоль будет выводиться квадрат значения i.

В данном случае вначале проверяется условие (i > 0) и если оно истинно (то есть возвращает true), то выполняется цикл. И вполне может быть ситуация, когда к началу выполнения цикла условие не будет выполняться. Например, переменная i изначально меньше 0, тогда цикл вообще не будет выполняться.

Есть другая форма цикла while - do..while:

   var i = -1
do{
println(i*i)
i--;
}
while(i > 0)


В данном случае вначале выполняется блок кода после ключевого слова do, а потом оценивается условие после while. Если условие истинно, то повторяется выполнение блока после do. То есть несмотря на то, что в данном случае переменная i меньше 0 и она не соответствует условию, тем не менее блок do выполнится хотя бы один раз.

Операторы continue и break

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

   for(n in 1..8){
if(n == 5) continue;
println(n * n)
}

В данном случае когда n будет равно 5, сработает оператор continue. И последующая инструкция, которая выводит на консоль квадрат числа, не будет выполняться. Цикл перейдет к обработке следующего элемента в массиве.

Иногда при некоторых условиях вовсе надо выйти из цикла, прекратить его выполнение. В этом случае применяется оператор break:

   for(n in 1..5){
if(n == 5) break;
println(n * n)
}

В данном случае когда n окажется равен 5, то с помощью оператора break будет выполнен выход из цикла. Цикл полностью завершится.

Массивы[править]

Массив представляет набор данных одного типа. В языке Kotlin массивы представлены типом Array.

При определении массива после типа Array в угловых скобках необходимо указать тип объектов, хранящихся в массиве.

  val numbers: Array<Int>

С помощью встроенной функции arrayOf() можно передать набор значений, которые будут составлять массив:

  val numbers: Array<Int> = arrayOf(1, 2, 3, 4, 5)

С помощью индексов можно обратиться к определенному элементу в массиве. Индексация начинается с нуля (первый элемент имеет индекс 0). Индекс указывается в квадратных скобках. Также можно переустановить значение элемента.

   val numbers: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val n = numbers[1]
numbers[2] = 7

Также инициализировать массив можно следующим образом:

   val numbers = Array(3, {5})

Здесь применяется конструктор класса Array. В этот конструктор передаются два параметра. Первый параметр указывает, сколько элементов будет в массиве. В данном случае 3 элемента. Второй параметр представляет выражение, которое генерирует элементы массива. Оно заключается в фигурные скобки. В данном случае в фигурных скобках стоит число 5, то есть все элементы массива будут представлять число 5. Таким образом, массив будет состоять из трех пятерок.

Для упрощения создания массива в Kotlin определены дополнительные типы:

  • BooleanArray
  • ByteArray
  • ShortArray
  • IntArray
  • LongArray
  • CharArray
  • FloatArray
  • DoubleArray

Данные типы позволяют создавать массивы для определенных типов. Например, тип IntArray позволяет определить массив объектов Int, а DoubleArray - массив объектов Double:

   val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5)
val doubles: DoubleArray = doubleArrayOf(2.4, 4.5, 1.2)

Двумерные массивы.

Определение двухмерных массивов аналогично простым массивам. Двухмерный массив можно представить в виде таблицы, где каждая строка - это отдельный массив, а ячейки строки - это элементы вложенного массива.
Например:
val table: Array<Array<Int>> = Array(3, { Array(5, {0}) })

В данном случае двухмерный массив будет иметь три элемента - три строки. Каждая строка будет иметь по пять элементов, каждый из которых равен 0.

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

   val table = Array(3, { Array(3, {0}) })
table[0] = arrayOf(1, 2, 3)
table[1] = arrayOf(4, 5, 6)
table[2] = arrayOf(7, 8, 9)

Для обращения к элементам подмассивов двухмерного массива необходимы два индекса. По первому индексу идет получение строки, а по второму индексу - столбца в рамках этой строки:

   val table = Array(3, { Array(3, {0}) })
table[0][1] = 6
val n = table[0][1]

Перебор массивов

Для перебора массивов применяется цикл for:

   val months: Array<String> = arrayOf("September", "October", "November")
for(month in months){
println(month)}

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

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

   val table: Array<Array<Int>> = Array(3, { Array(3, {0}) })
table[0] = arrayOf(1, 2, 3)
table[1] = arrayOf(4, 5, 6)
table[2] = arrayOf(7, 8, 9)
for(row in table){
for(cell in row){
print("$cell \t")
}
println()
}

С помощью внешнего цикла for(row in table) пробегаемся по всем элементам двухмерного массива, то есть по строкам таблицы.
Каждый из элементов двухмерного массива сам представляет массив, поэтому можyj пробежаться по этому массиву и получить из него непосредственно те значения, которые в нем хранятся.

Объектно-ориентированное программирование в Kotlin[править]

Классы и объекты.[править]

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

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

Для определения класса применяется ключевое слово class, после которого идет имя класса. После имени класса в фигурных скобках определяется тело класса. Если класс не имеет тела, то фигурные скобки можно опустить. Например, определим класс, который представляет человека. Определить класс моо двумя способами:
1 способ

   class Person

2 способ

   class Person { }

Класс представляет новый тип данных, потому можно определять переменные этого типа:

   fun main(args: Array<String>) {
val tom: Person
val bob: Person
val alice: Person
}
class Person

В функции main определены три переменных типа Person. Функция main в Kotlin не помещается в отдельных класс, а всегда определяется вне какого-либо класса.

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

   val tom: Person = Person()

Часть кода после знака равно Person() представляет вызов конструктора, который создает объект класса Person. До вызова конструктора переменная класса не указывает ни на какой объект.

Например, создадим три объекта класса Person:

   fun main(args: Array<String>) {
val tom: Person = Person()
val bob: Person = Person()
val alice: Person = Person()
}
class Person

В Kotlin классы могут содержать ряд компонентов:

  • конструкторы и инициализаторы
  • функции
  • свойства
  • вложенные классы
  • объявления объектов

Свойства. Конструкторы.[править]

Каждый класс может хранить некоторые данные или состояние (state) в виде свойств. Свойства представляют переменные, определенные на уровне класса с ключевыми словами val и var. Если свойство определено с помощью val, то значение такого свойства можно установить только один раз, то есть оно immutable. Если свойство определено с помощью var, то значение этого свойства можно многократно изменять.

Свойство должно быть инициализировано, то есть обязательно должно иметь начальное значение.
Например:

   class Person{
var name: String = "Tom"
var age: Int = 18
}

В данном случае в классе Person, который представляет человека, определены свойства name (имя человека) и age (возраст человека). И эти свойства инициализированы начальными значениями.

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

   fun main(args: Array<String>) {
val bob: Person = Person()
println(bob.name)
println(bob.age)
bob.name = "Bob"
bob.age = 25
println(bob.name)
println(bob.age)
}
class Person{
var name: String = "Tom"
var age: Int = 18
}

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

   val personName : String = bob.name

Установка значения свойства:

   bob.name = "Bob"

Геттеры и сеттеры
Для каждого свойства можно определять геттер и сеттер.
Геттер
Геттер управляет получением значения свойства и определяется с помощью слова get:

   class Person{
var name: String = "Tom"
var age: Int = 18
val info: String
get() = "Name: $name Age: $age"
}

Здесь свойство info определяет блок get, который возвращает информацию с именем и возрастом человека.
При получении значения свойства получаем именно те данные, которые возвращаются блоком get.

Сеттер

Сеттер определяет логику установки значения. Он определяется с помощью слова set.

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

   class Person{
var name: String = "Tom"
var age: Int = 18
set(value){
if((value>0) and (value <110))
field = value
}
val info: String
get() = "Name: $name Age: $age"
}

Блок set определяется также, как и блок get, сразу после свойства. При этом блок set представляет собой функцию, которая принимает один параметр - value, через этот параметр передается устанавливаемое значение.

В блоке set происходит проверка: входит ли устанавливаемое значение в диапазон допустимых значений. Если входит, то есть если значение корректно, то значение передается объекту field. Если значение некорректно, то свойство просто сохраняет свое предыдущее значение.

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

Можно использовать одновременно и геттер, и сеттер:

   class Person{
var name: String = "Tom"
get(){
return field.toUpperCase()
}
set(value){
if(value.length > 2)
field = value
}
var age: Int = 18
set(value){
if((value > 0) and (value < 110))
field = value
}
val info: String
get() = "Name: $name Age: $age"
}

Функции.[править]

Функции в Kotlin объявляются с помощью ключевого слова fun. Функции определяют поведение объектов данного класса. Такие функции также называют member functions или функции-члены класса.
Например:

   class Person(val name: String, val age: Int){
fun sayHello(){
println("Hello")
}
fun go(location: String){
println("$name goes to $location")
}
fun getInfo() : String{
return "Name: $name Age: $age"
}
}

Member function определяется также как и обычные функции. В примере, описанном выше, в классе Person определена функция sayHello(), которая выводит на консоль строку "Hello". Вторая функция - go отражает движение объекта Person к определенному местоположению. Местоположение передается через параметр location. И третья функция getInfo возвращает информацию о текущем объекте в виде строки.

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

Для обращения к функциям класса необходимо использовать имя объекта, после которого идет название функции и в скобках значения для параметров этой функции:

   fun main(args: Array<String>) {
val bob: Person = Person("Bob", 23)
bob.sayHello()
bob.go("the shop")
bob.go("the cinema")
println(bob.getInfo())
}

С более подробной информацией Вы можете ознакомиться на официальном сайте: Документация языка Kotlin

Пакеты и импорт.[править]

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

Для определения пакета применяется ключевое слово package, после которого идет имя пакета:

   package account

Определение пакета помещается в самое начало файла. И все содержимое файла рассматривается как содержимое этого пакета.

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

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.*
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

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

Пример:
Добавим в проект новый файл account.kt и определим в нем следующий код:

   package account
fun transfer(from: Account, to: Account, sum: Int){
if(from.sum >= sum){
from.sum -= sum
to.sum += sum
println("Operation has been finished")
}
else
println("Invalid operation")
}
class Account(var id: String, var sum: Int){
fun displayInfo(){
println("Account $id Sum: $sum")
}
}

Пакет называется account. Он содержит класс Account, представляющий банковский счет, и функцию transfer, которая реализовывает перевод средств между двумя счетами.

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

   import account.*

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

Откройте файл, который определяет функцию main, и используйте в нем функционал пакета account:

   import account.*
fun main(args: Array<String>) {
val acc1 = Account("123456", 5500)
val acc2 = Account("456778", 4000)
transfer(acc1, acc2, 2500)
acc1.displayInfo()
acc2.displayInfo()
}

Поскольку в начале файла импортированы все типы из пакета account, то мы можем использовать класс Account и функцию transfer в функции main.
Также можно импортировать типы, определенные в пакете, по отдельности:

   import account.transfer
import account.Account

Более подробную информацию о пакетах Kotlin Вы можете найти на официальном сайте Библиотеки Kotlin

Модификаторы видимости.[править]

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

  • private: классы, объекты, интерфейсы, а также функции и свойства, определенные вне класса, с этим модификатором видны только в том файле, в котором они определены. Члены класса с этим модификатором видны только в рамках своего класса.
  • protected: члены класса с этим модификатором видны в классе, в котором они определены, и в классах-наследниках.
  • internal: классы, объекты, интерфейсы, функции, свойства, конструкторы с этим модификатором видны в любой части модуля, в котором они определены. Модуль представляет набор файлов Kotlin, скомпилированных вместе в одну структурную единицу.
  • public: классы, функции, свойства, объекты, интерфейсы с этим модификатором видны в любой части программы. (При этом если функции или классы с этим модификатором определены в другом пакете их все равно нужно импортировать)

Если модификатор видимости явным образом не указан, то применяется модификатор public.
Пример:

   package example
fun sayHello() {} // функция видна везде, так как модификатор по умолчанию - public
private fun display() {} // функция видна только в текущем файле
internal val weight = 6 // переменная видна в пределах модуля
public var age: Int = 18 // свойство видно в любой части программы
private set // но сеттер доступен только в пределах этого же файла
private class Person { } // класс доступен только в пределах этого же файла

Для установки уровня видимости модификатор ставится перед ключевыми словами class/var/val/fun/set в самом начале определения класса/свойства/переменной/функции/сеттера.

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

   class Person(_name: String, _id: Int){
var name: String // свойство публичное и видно везде
private set // сеттер доступен только внутри этого класса
private val id: Int // свойство доступно из этого класса
internal fun display(){ // функция видна в текущем модуле
println("Id: $id Name: $name")
}
init {
name = _name
id = _id
}
}

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

   class Person(internal val name: String, private val id: Int){
internal fun display(){ // функция видна в текущем модуле
println("Id: $id Name: $name")
}
}

Также можно указать модификатор у конструкторов, например, мы хотим сделать конструктор класса закрытым (то есть с модификатором private):

   class User private constructor(val name: String){
var age: Int = 0
internal constructor(name: String, _age: Int): this(name){
age = _age
}
}

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

С более подробной информацией Вы можете ознакомиться на официальном сайте Kotlin: Модификаторы видимости

Наследование. Перечисления enums.[править]

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

  • Базовый класс (класс-родитель, родительский класс), который определяет базовую функциональность.
  • Производный класс (класс-наследник, подкласс), который наследует функциональность базового класса и может расширять или модифицировать ее.

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

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

Пример:

   open class Person(val name: String)
class Employee(val company: String, name: String): Person(name)

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

   open class Person()
class Employee: Person()

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

   open class Person(val name: String)
class Employee: Person{
var company: String="undefined"
constructor(name: String, comp:String) : super(name){
company = comp
}
}

Применение классов:

   fun main(args: Array<String>) {
val alice: Person = Person("Alice")
val kate: Employee = Employee("Kate", "Google")
val liza: Person = Employee("Liza", "Apple")
}
open class Person(val name: String)
class Employee: Person{
var company: String="undefined"
constructor(name: String, comp:String) : super(name){
company = comp
}
}

Поскольку объект Employee в то же время является и объектом класса Person в силу отношения наследования, то можно переменной типа Person передать объект Employee:

   val liza: Person = Employee("Liza", "Apple")

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

Также отметим, что все классы по умолчанию наследуются от класса Any, даже если класс Any явным образом не указан в качестве базового. Поэтому любой класс уже по умолчанию будет иметь все свойства и функции, которые определены в классе Any. Поэтому все классы по умолчанию уже будут иметь такие функции как equals, toString, hashcode.

Интерфейсы.[править]

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

Для определения интерфейса применяется ключевое слово interface.
Пример:

   interface Movable{
fun move() // определение функции без реализации
fun stop(){ // определение функции с реализацией по умолчанию
println("Остановка")
}
}

Интерфейс содержит две функции. Функция move() представляет абстрактный метод - она не имеет реализации. Вторая функция stop имеет реализацию по умолчанию.

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

   class Car : Movable{
override fun move(){
println("Машина едет")
}
}
class Aircraft : Movable{
override fun move(){
println("Самолет летит")
}
override fun stop(){
println("Приземление")
}
}

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

В данном случае интерфейс Movable представляет функцонал транспортного средства и определяет функции move (движение) и stop (остановка).

Класс Car представляет машину и применяет интерфейс Movable. Так как и интерфейс содержит абстрактный метод move, то класс Car обязательно должен его реализовать. Функцию stop класс Car может не реализовать, так как она уже содержит реализацию по умолчанию. При реализации функций перед ними ставится ключевое слово override.

Класс Aircraft представляет самолет и тоже применяет интерфейс Movable. При этом класс Aircraft реализует обе функции интерфейса.

Впоследствии в программе мы можем рассматривать объекты классом Car и Aircraft как объекты Movable:

   fun main(args: Array<String>) {
val m1: Movable = Car()
val m2: Movable = Aircraft()
// val m3: Movable = Movable() напрямую объект интерфейса создать нельзя
m1.move()
m1.stop()
m2.move()
m2.stop()
}

Рассмотрим еще пример. Определим интерфейс Info, который объявляет ряд свойств:

   interface Info{
val model: String
get() = "Undefined"
val number: String
}

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

Для реализации интерфейса возьмем выше определенный класс Car:

   class Car(mod: String, num: String) : Movable, Info{
override val number: String
override val model: String
init{
number = num
model = mod
}
override fun move(){
println("Машина едет")
}
}

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

Применение класса:

   fun main(args: Array<String>) {
val m1: Car = Car("Tesla", "2345SDG")
println(m1.model)
println(m1.number)
m1.move()
m1.stop()
}

Более подробную информацию Вы можете найти здесь: Интерфейсы