В чем разница между val и var в kotlin
Для хранения данных в программе в Kotlin, как и в других языках программирования, применяются переменные. Переменная представляет именованный участок памяти, который хранит некоторое значение.
Каждая переменная характеризуется определенным именем, типом данных и значением. Имя переменной представляет поизвольный идентификатор, который может содержать алфавитно-цифровые символы или символ подчеркивания и должен начинаться либо с алфавитного символа, либо со знака подчеркивания. Для определения переменной можно использовать либо ключевое слово val , либо ключевое слово var .
Формальное определение переменной:
val|var имя_переменной: тип_переменной
Вначале идет слово val или var, затем имя переменной и через двоеточие тип переменной.
Например, определим переменную age:
val age: Int
То есть в данном случае объявлена переменная age, которая имеет тип Int . Тип Int говорит о том, что переменная будет содержать целочисленные значения.
После определения переменной ей можно присвоить значение:
fun main()
Для присвоения значения переменной используется знак равно. Затем мы можем производить с переменной различные операции. Например, в данном случае с помощью функции println() значение переменной выводится на консоль. И при запуске этой программы на консоль будет выведено число 23.
Присвоение значения переменной должно производиться только после ее объявления. И также мы можем сразу присвоить переменной начальное значение при ее объявлении. Такой прием называется инициализацией:
fun main()
Однако обязательно надо присвоить переменной некоторое значение до ее использования:
fun main() < val age: Int println(age)// Ошибка, переменная не инициализирована >
Изменяемые и неизменяемые переменные
Выше было сказано, что переменные могут объявляться как с помощью слова val , так и с помощью слова var . В чем же разница между двумя этими способами?
С помощью ключевого слова val определяется неизменяемая переменная (immutable variable). То есть мы можем присвоить значение такой переменной только один раз, но изменить его после первого присвоения мы уже не сможем. Например, в следующем случае мы получим ошибку:
fun main() < val age: Int age = 23 // здесь норм - первое присвоение age = 56 // здесь ошибка - переопределить значение переменной нельзя println(age) >
А у переменной, которая определена с помощью ключевого слова var мы можем многократно менять значения (mutable variable):
fun main()
Поэтому если не планируется изменять значение переменной в программе, то лучше определять ее с ключевым словом val .
Определение констант
Также Kotlin поддерживает константы времени компиляции. Для их определения применяются ключевые слова const val :
const val maxAge = 120 // константа fun main()
В данном случае maxAge является константой.
Отличительной особенностью констант является то, что они на стадии компиляции должны иметь некоторое значение, и это значение изменить нельзя. Это накладывает на использование констант ряд ограничений:
-
Естественно нельзя изменить значение константы:
const val maxAge = 120 // константа fun main() < maxAge = 1500 // ошибка println(maxAge) >
fun main() < const val maxAge = 120 // ошибка println(maxAge) >
Также стоит отметит отличие val -переменных от констант ( const val ): значение val -переменных устанавливается во время выполнения, а значение констант — во время компиляции. Значение val -переменной также нельзя изменить после установки, однако мы можем объявить val -переменную, а потом дальше в программе присвоить ей значение. Константе же необходимо присвоить значение сразу при определении.
Переменные и базовые классы данных в Kotlin
Переменные в Kotlin объявляются с помощью ключевых слов var или val, за которыми идет имя переменной, через двоеточие указывается ее тип – класс данных.
Переменной, объявленной через val , значение в программе можно присвоить только один раз. В дальнейшем значение можно только читать, val-переменные являются неизменяемыми.
fun main() { var a: Int a = 5 println(a) }
Приведенный выше код правильный, но IntelliJ будет подсказывать, что его можно улучшить. Поскольку в программе мы не меняем значение переменной, ее следовало бы объявить через val .

Однако поступим иначе. В процессе выполнения программы будем менять значение переменной:
fun main() { var a: Int a = 5 println(a) a = a + 10 println(a) }
Тут тоже не все гладко. IntelliJ советует совместить объявление переменной с присвоением ей значения.

Действительно, это уместно в данном случае. Ведь мы, например, не запрашиваем значение с ввода после выполнения каких-либо предварительных действий.
fun main() { var a: Int = 5 println(a) a = a + 10 println(a) }
Теперь IntelliJ советует избавиться от Int , так как по значению и так понятен тип переменной.

То есть Kotlin способен вывести тип переменной из присвоенных ей данных. Это называется автоматическим определением типов. Поэтому приводим код к такому виду:
fun main() { var a = 5 println(a) a = a + 10 println(a) }
Это не значит, что переменная стала без типа. Kotlin – статически типизированный язык. Просто тип переменной был выведен, исходя из ее значения. Чтобы увидеть тип переменной в IntelliJ, надо установить на нее курсор и нажать Ctrl + Shift + P .
Осталось последнее замечание. IntelliJ считает, что добавление значения к переменной лучше записывать в сокращенной форме:
fun main() { var a = 5 println(a) a += 10 println(a) }
В качестве вещественного типа по умолчанию используется класс Double :
fun main() val a = 3.4 println(a) >

В данном случае тип переменной автоматически определится как Double. В Kotlin есть и другие числовые типы помимо Double и Int – это Float , Long , Short , Byte .
fun main() { val a = true val b = false var c: Boolean = a > b println(c) // true c = a b println(c) // false }
В Kotlin есть как строковый, так и символьный типы:
fun main() { val s = "Hello" val s2: String val c = 'W' val c2: Char = s[0] s2 = s + c println(s2) // HelloW println(c2) // H }
В Kotlin есть классы коллекций – списки (List), словари (Map), множества (Set).
Константы
Переменные, объявленные через val , – не константы. Ведь им не обязательно сразу присваивать значение. Это происходит в процессе выполнения программы. Например:
fun main() { val a: String = readln() println(a) }
Здесь более очевидно, что до компиляции значение переменной a неизвестно. Присвоение значения происходит в процессе выполнения программы, и значением может быть любая строка.
С другой стороны, в Kotlin есть, так сказать, настоящие константы – константы времени компиляции. Их значение известно заранее, «вшивается» в программу в момент ее компиляции.
Объявление и определение констант происходит за пределами функции. Потому что код внутри функций выполняется при исполнении программы, а сигнатуры функций и полей должны быть известны на момент компиляции.
const val N: Int = 10 fun main() println(N) >
Модификатор const не получится использовать внутри функции. Это не значит, что все переменные за пределами функции должны быть константами. Там могут быть и обычные переменные. Очевидно, сочетания const var быть не может.
Константы могут принадлежать только числовым типам, строковому, символьному, а также Boolean .
Nullable-типы
В Kotlin обычным типам нельзя присвоить литерал null – значение, говорящее, что переменная не связана с обычным объектом. Подобное ограничение в Kotlin призвано обеспечить nullable-безопасность, то есть таким образом избегается ряд ошибок при выполнении программы. Например, определенная функция не предполагает, что ей передадут null , и не умеет обрабатывать это значение, но получает null . В этом случае в процессе выполнения программы будет выброшено исключение.
Во избежании подобного, обработка потенциально возможного null перекладывается на плечи программиста в принудительном порядке. Либо он напишет функцию так, чтобы она могла принимать и обрабатывать null , либо проверит значение на неравенство null до его передачи в качестве аргумента в функцию.
С другой стороны, в Kotlin у каждого типа без поддержки null имеется аналогичный ему с поддержкой null-значения. При объявлении типа, второй отличается от первого знаком вопроса в конце. Например, есть два строковых типа: String – без поддержки null и String? с поддержкой null .

На скрине показано, что попытка присвоить null переменным a и c приводит к ошибке. В то же время, для nullable-переменных это позволительно, хотя не обязательно. Их значениями также могут быть числа и строки, как у обычных переменных.
При этом в функцию, параметр которой не поддерживает null , передать nullable-аргумент нельзя. Компилятор Котлина не даст скомпилировать такую программу.

Встроенные функции readln() и readlnOrNull() возвращают строки, прочитанные с ввода. Функция readlnOrNull() потенциально может вернуть не только строку, но и значение null . Поэтому возвращаемый из нее тип String? . Переменную типа String? мы не можем передать в нашу функцию exPrint() , даже если значение этой переменной не будет null . Котлин – строго типизированный язык, поэтому String? и String для него – разные типы.
Для обработки nullable-переменных в Kotlin предусмотрено несколько особых операторов, которые будут рассмотрены в следующем уроке.
Быстрое вступление
Исходный код хранится в файлах с расширением .kt (почему не .kot?).
В языке используется «кошачий принцип» — чем меньше печатаешь код, тем больше времени останется на сон.
Например, необязательно ставить точку с запятой в конце выражения. Так поступает JavaScript и многие новые языки программирования.
// и так сойдёт val x = 1 val y = 2
В Kotlin есть единственное место, где точка с запятой обязательна — в перечислении enum
Необязательно объявлять тип переменной, если из контекста понятно её предназначение. Если брать предыдущий пример, то по значению 1 можно догадаться, что переменная является типом Int.
Узнать тип переменной в студии можно через комбинацию клавиш Ctrl+Shift+P
Kotlin не делит типы на примитивные и их обёртки. Например, есть тип Int вместо int и Integer.
Можно сразу вызывать println вместо длинного System.out.println. Стандартная библиотека Kotlin включает в себя популярные методы Java для быстрого доступа.
Например, можно быстро получить содержимое файла с сервера через метод URL.readText():
// не вызывать в основном потоке! val address = "http://example.com" val someText = URL(address).readText()
Объявление переменных
В Java мы сначала указываем тип переменной, а потом её имя. В Kotlin немного не так. Сначала вы указываете ключевое слово val или var, затем имя переменной и по желанию можете указать тип переменной.
val kitty = "Васька" val age = 7 // необязательно, но укажем тип val weight: Int = 3 val catName: String = "Мурзик" val actionBar = supportActionBar // объект ActionBar в активности без new
Если вы не инициализируете переменную, то тип указать нужно обязательно.
val age: Int age = 7
Иногда тип указывать обязательно.
val a: Any = 12 val context: Context = activity
Ключевое слово val (от слова value) ссылается на неизменяемую переменную, что соответствует ключевому слову final в Java.
// Java final String name = "Васька"; // Kotlin val name = "Васька"
А часто используемое выражение в Java можно заменить на конструкцию с ключевым словом const (применимо только к базовым типам):
// Java public static final String CAT_TALK = "meow"; // Kotlin const val CAT_TALK = "meow"
Для обычных изменяемых переменных используется ключевое слово var (от слова variable).
Рекомендуется всегда использовать val, если это позволяет логика программы.
При этом нужно помнить, что хотя ссылка val неизменяема, сам объект может быть изменяемым:
val cats = arrayListOf("Васька") cats.add("Барсик")
При использовании var вы можете не указывать тип переменной, но это не значит, что вы можете использовать его не по назначению, это вам не PHP.
var answer = 42 // так нельзя answer = "нет ответа"
Возникает вопрос, а в чём разница между val и const? В некоторых случаях поведение будем одинаковым, но есть разница в подходе. Мы можем использовать const для константы на этапе компиляции, а для val мы можем присвоить неизменяемое значение на этапе рантайма.
const val catName = "Мурзик" // можно val catName = "Мурзик" // тоже можно const val catName = getCompanyName() // нельзя, нужно сразу присвоить значение без вычислений val catName = getCompanyName() // а так можно
Когда и где использовать val и const? Рассмотрим пример.
// какой-то класс MyClass < companion object < const val FILE_EXTENSION = ".png" val FILENAME: String get() = "img_" + System.currentTimeMillis() + FILE_EXTENSION >>
Расширение класса можно считать константой. А имя файла вычисляется на основе времени и его нельзя использовать как константу.
Ключевое слово inline
Ещё одно новое ключевое слово в Kotlin.
Псевдоним для импорта
Чтобы не путаться с именами классов из разных пакетов, можно присвоить новый псевдоним.
import android.os.Bundle import ru.alexanderklimov.cat.Bundle as CatBundle fun getBundle()
typealias
Псевдоним типа позволяет определить для существующего типа альтернативное имя, которое может использоваться в коде. Если в вашем коде используется функциональный тип, например такой, как (Double) -> Double, — вы сможете определить псевдоним, который будет использоваться вместо этого типа, чтобы ваш код лучше читался. Псевдонимы типов определяются ключевым словом typealias.
typealias DoubleConversion = (Double) -> Double
При помощи typealias можно определять альтернативные имена для любых типов, не только функциональных. Например, следующее определение typealias CatArray = Array позволяет обращаться к типу по имени CatArray вместо Array .
Исключения
Создадим собственное исключение.
class CustomException(message:String): Exception(message)
Теперь можем кинуть созданное исключение в своём коде в нужном месте.
throw CustomException("Threw custom exception")
Свойства
Свойства в классах Kotlin могут быть объявлены либо как изменяемые (mutable) и неизменяемые (read-only) — var и val соответственно.
class Address
Для того чтобы воспользоваться свойством, просто обратитесь к нему по имени.
fun copyAddress(address: Address): Address < val result = Address() // в Kotlin нет никакого слова `new` result.name = address.name // вызов методов доступа result.street = address.street // . return result >
Геттеры и сеттеры
Полный синтаксис объявления свойства выглядит так:
Инициализатор property_initializer , геттер и сеттер можно не указывать. Также необязательно указывать тип свойства, если он может быть выведен из инициализатора или из возвращаемого типа геттера.
var initialized = 1 // имеет тип Int, стандартный геттер и сеттер // var allByDefault // ошибка: необходима явная инициализация, // предусмотрены стандартные геттер и сеттер
Синтаксис объявления констант имеет два отличия от синтаксиса объявления изменяемых переменных: во-первых, объявление константы начинается с ключевого слова val вместо var , а во-вторых, объявление сеттера запрещено.
val simple: Int? // имеет тип Int, стандартный геттер, // должен быть инициализирован в конструкторе val inferredType = 1 // имеет тип Int и стандартный геттер
Вы можете самостоятельно определить методы доступа для свойства. Если вы определяете пользовательский геттер, он будет вызываться каждый раз, когда вы обращаетесь к свойству (таким образом, вы можете реализовать вычисляемое свойство). Вот пример пользовательского геттера:
class Rectangle(val width: Int, val height: Int) < val area: Int get() = this.width * this.height // тип свойства необязателен, поскольку он может быть выведен из возвращаемого типа геттера >
Вы можете опустить тип свойства, если его можно определить с помощью геттера.
val area get() = this.width * this.height
Если вы определяете пользовательский сеттер, он будет вызываться каждый раз, когда вы присваиваете значение свойству, за исключением его инициализации. Пользовательский сеттер выглядит так:
var stringRepresentation: String get() = this.toString() set(value) < setDataFromString(value) // парсит строку и устанавливает // значения для других свойств >
По договорённости, имя параметра сеттера — value , но вы можете использовать любое другое.
Если вам нужно изменить область видимости метода доступа или пометить его аннотацией, при этом не внося изменения в реализацию по умолчанию, вы можете объявить метод доступа без объявления его тела.
var setterVisibility: String = "abc" private set // сеттер имеет private доступ и стандартную реализацию var setterWithAnnotation: Any? = null @Inject set // аннотирование сеттера с помощью Inject
Теневые поля
В Kotlin поле используется только как часть свойства для хранения его значения в памяти. Поля не могут быть объявлены напрямую. Однако, когда свойству требуется теневое поле (backing field), Kotlin предоставляет его автоматически. На это теневое поле можно обратиться в методах доступа, используя идентификатор field :
var counter = 0 // инициализатор назначает резервное поле напрямую set(value) < if (value >= 0) field = value // значение при инициализации записывается // прямиком в backing field // counter = value // ERROR StackOverflow: Использование 'counter' сделало бы сеттер рекурсивным >
Идентификатор field может быть использован только в методах доступа к свойству.
Теневое поле будет сгенерировано для свойства, если оно использует стандартную реализацию как минимум одного из методов доступа, либо если пользовательский метод доступа ссылается на него через идентификатор field .
Например, в примере ниже не будет никакого теневого поля:
val isEmpty: Boolean get() = this.size == 0
Теневые свойства
Если вы хотите предпринять что-то такое, что выходит за рамки вышеуказанной схемы неявного теневого поля, вы всегда можете использовать теневое свойство (backing property).
private var _table: Map? = null public val table: Map get() < if (_table == null) < _table = HashMap() // параметры типа вычисляются автоматически // (ориг.: "Type parameters are inferred") >return _table ?: throw AssertionError("Set to null by another thread") >
On the JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead. —>
В JVM: доступ к приватным свойствам со стандартными геттерами и сеттерами оптимизируется таким образом, что вызов функции не происходит.
Константы времени компиляции
Если значение константного (read-only) свойства известно во время компиляции, пометьте его как константы времени компиляции, используя модификатор const . Такие свойства должны соответствовать следующим требованиям:
- Находиться на самом высоком уровне или быть членами объявления object или вспомогательного объекта;
- Быть проинициализированными значением типа String или значением примитивного типа;
- Не иметь переопределённого геттера.
Такие свойства могут быть использованы в аннотациях.
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" @Deprecated(SUBSYSTEM_DEPRECATED) fun foo()
Свойства и переменные с поздней инициализацией
Обычно, свойства, объявленные non-null типом, должны быть проинициализированы в конструкторе. Однако часто бывает так, что делать это неудобно. К примеру, свойства могут быть инициализированы через внедрение зависимостей или в установочном методе (ориг.: setup method) юнит-теста. В таком случае вы не можете обеспечить non-null инициализацию в конструкторе, но всё равно хотите избежать проверок на null при обращении внутри тела класса к такому свойству.
Для того чтобы справиться с такой задачей, вы можете пометить свойство модификатором lateinit .
public class MyTest < lateinit var subject: TestSubject @SetUp fun setup() < subject = TestSubject() >@Test fun test() < subject.method() // объект инициализирован, проверять на null не нужно >>
Такой модификатор может быть использован только с var свойствами, объявленными внутри тела класса (не в основном конструкторе, и только тогда, когда свойство не имеет пользовательских геттеров и сеттеров), со свойствами верхнего уровня и локальными переменными. Тип такого свойства должен быть non-null и не должен быть примитивным.
Доступ к lateinit свойству до того, как оно проинициализировано, выбрасывает специальное исключение, которое чётко обозначает свойство, к которому осуществляется доступ, и тот факт, что оно не было инициализировано.
Проверка инициализации lateinit var
Чтобы проверить, было ли проинициализировано lateinit var свойство, используйте .isInitialized метод ссылки на это свойство.
if (foo::bar.isInitialized)
Эта проверка возможна только для лексически доступных свойств, то есть объявленных в том же типе, или в одном из внешних типов, или глобальных свойств, объявленных в том же файле.
Переопределение свойств
Делегированные свойства
Самый простой тип свойств просто считывает (или записывает) данные из теневого поля. Тем не менее с пользовательскими геттерами и сеттерами мы можем реализовать совершенно любое поведение свойства. Где-то между простотой первого вида и разнообразием второго существуют общепринятые шаблоны того, что могут делать свойства. Несколько примеров: вычисление значения свойства при первом доступе к нему (ленивые значения), чтение из ассоциативного списка с помощью заданного ключа, доступ к базе данных, оповещение listener’а в момент доступа.
Такие распространённые поведения свойств могут быть реализованы в виде библиотек с помощью делегированных свойств.
© 2015—2023 Open Source Community
