В Kotlin, как и во многих других языках, переменные могут быть изменяемыми и неизменяемыми. В статье про базовый синтаксис рассказывалось, что для объявления изменяемой переменной следует использовать ключевое слово var
, а неизменяемой - val
.
Несмотря на то, что мы говорим неизменяемая, мы не подразумеваем, что это константа. Неважно какое ключевое слово используется при объявлении переменной. В любом случае такие переменные могут быть инициализированы во время выполнения программы, что противоречит понятию константы. Ведь что такое константа? Значение, неизменное по своей природе.
Что же делать, если нужно объявить константу? Использовать модификатор const
совместно с ключевым словом val
. Но есть несколько правил, которым необходимо следовать.
Переменные, отмеченные модификатором const
, также называют константами времени компиляции. Это означает, что значения таких переменных известны во время компиляции. Отсюда следует, что они должны соответствовать следующим требованиям:
- находиться на самом верхнем уровне (вне класса) или быть членом объекта (
object
илиcompanion object
); - тип данных должен соответствовать одному из примитивных (например, String);
- не иметь геттера.
Разберем на примере.
1
2
3
4
5
6
7
8
class SomeClass {
companion object {
const val FILE_EXTENSION = ".jpg"
val FILENAME: String
get() = "Img_" + System.currentTimeMillis() + FILE_EXTENSION
}
}
Здесь мы в объекте-компаньоне объявили константу, значением которой является расширение фотографии. Помимо этого мы объявили неизменяемую переменную, которая будет хранить имя фотографии и инициализироваться с помощью get-метода.
Мы заранее (до компиляции) знаем, что расширение у всех фотографий будет одно и то же. Нам не нужно его вычислять. Поэтому логично будет его объявить как константу.
Имя же фотографии, несмотря на то что оно уникально для каждого отдельного файла, заранее неизвестно. Чтобы его задать, нам потребуется вычислить время, в которое был сделан снимок. То есть значение выбирается во время выполнения программы. Поэтому используется ключевое слово val
.
После компиляции кода везде, где использовалась переменная-константа будет произведено замещение: вместо имени переменной будет подставлено значение этой переменной. Переменная, которая хранит имя файла останется как есть.
Если декомпилировать приведенный выше код, то получим следующее:
1
2
3
public final String getFILENAME() {
return "Img_" + System.currentTimeMillis() + ".png";
}
Companion object vs val верхнего уровня
Скорее всего может возникнуть вопрос, как стоит объявлять свои константы в Kotlin: при помощи companion object
или вне класса?
На самом деле оба эти подхода приемлемы. Однако, использование companion object
может быть излишним: компилятор Kotlin преобразует companion object
во вложенный класс. Слишком много кода для простой константы.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// исходный код Kotlin
class SomeClass {
companion object {
const val FILE_EXTENSION = ".jpg"
}
// эквивалент на Java
public final class SomeClass {
@NotNull
public static final String FILE_EXTENSION = ".jpg";
public static final SomeClass.Companion Companion = new SomeClass.Companion((DefaultConstructorMarker)null);
public static final class Companion {
private Companion() {
}
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
Объявление константы вне класса приведёт к гораздо менее раздутому результату.
1
2
3
4
5
6
7
8
// исходный код Kotlin
const val FILE_EXTENSION = ".jpg"
// эквивалент на Java
public final class SomeClass {
@NotNull
public static final String FILE_EXTENSION = ".jpg";
}
Таким образом, если вам не требуется поведение, специфичное для companion object
, объявляйте константы вне класса, так как это будет способствовать более эффективному байт-коду. Да и сам синтаксис объявления констант вне класса более чистый и читабельный.
Если вы хотите сгруппировать несколько констант в одном месте, то используйте для этого объекты:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// исходный код Kotlin
object Tree {
const val pine = "Pine"
const val apple = "Apple tree"
const val birch = "Birch tree"
}
// эквивалент на Java
public final class Tree {
@NotNull
public static final String pine = "Pine";
@NotNull
public static final String apple = "Apple tree";
@NotNull
public static final String birch = "Birch tree";
public static final Tree INSTANCE;
private Tree() {
}
static {
Tree var0 = new Tree();
INSTANCE = var0;
}
}
Вывод
- Значение переменной
const val
неизменно и используется только для чтения. - В отличие от
val
, значениеconst val
должно быть известно во время компиляции.