type | layout | title | category | url |
---|---|---|---|---|
doc |
reference |
Перегрузка операторов |
Syntax |
Язык Kotlin позволяет нам реализовывать предопределённый набор операторов для наших типов. Эти операторы имеют фиксированное символическое представление (вроде +
или *
) и фиксированные приоритеты. Для реализации оператора мы предоставляем функцию-член или функцию-расширение с фиксированным именем и с соответствующим типом, т. е. левосторонним типом для бинарных операций или типом аргумента для унарных оперций. Функции, которые перегружают операторы, должны быть отмечены модификатором operator
.
Далее мы опишем соглашения, которые регламентируют перегрузку операторов для разных типов операторов.
Выражение | Транслируется в |
---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
Эта таблица демонстрирует, что когда компилятор обрабатывает, к примеру, выражение +a
, он оcуществляет следующие действия:
- Определяется тип выражения
a
, пусть это будетT
. - Смотрится функция
unaryPlus()
с модификаторомoperator
без параметров для приёмника типаТ
, т. е. функция-член или функция расширения. - Если функция отсутствует или неоднозначная, то это ошибка компиляции.
- Если функция присутствует и её возвращаемый тип есть
R
, выражение+a
имеет ТипR
.
Примечание: эти операции, как и все остальные, оптимизированы для основных типов и не вносят накладных расходов на вызовы этих функций для них.
Например, вы можете перегрузить оператор унарного минуса:
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
println(-point) // выведет "(-10, -20)"
Выражение | Транслируется в |
---|---|
a++ |
a.inc() + see below |
a-- |
a.dec() + see below |
Функции inc()
и dec()
должны возвращать значение, которое будет присвоено переменной, к которой была применёна
операция ++
или --
. Они не должны пытаться изменять сам объект, для которого inc
или dec
были вызваны.
Компилятор осуществляет следующие шаги для разрешения операторов в постфиксной форме, например для a++
:
- Определяется тип переменной
a
, пусть это будетT
. - Смотрится функция
inc()
с модификаторомoperator
без параметров, применимая для приёмника типаТ
. - Проверяется, что возвращаемый тип такой функции является подтипом
T
.
Эффектом вычисления будет:
- Загружается инициализирующее значение
a
во временную переменнуюa0
, - Результат выполнения
a.inc()
сохраняется вa
, - Возвращается
a0
как результат вычисления выражения (т.е. значение до инкремента).
Для a--
шаги выполнения полностью аналогичные.
Для префиксной формы ++a
или --a
разрешение работает подобно, но результатом будет:
- Присвоение результата вычисления
a.inc()
непосредственноa
, - Возвращается новое значение
a
как общий результат вычисления выражения.
Выражение | Транслируется в |
---|---|
a + b |
a.plus(b) |
a - b |
a.minus(b) |
a * b |
a.times(b) |
a / b |
a.div(b) |
a % b |
a.rem(b) , a.mod(b) (устаревшее) |
a..b |
a.rangeTo(b) |
Для перечисленных в таблице операций компилятор всего лишь разрешает выражение из колонки Транслируется в.
Отметим, что операция rem
поддерживается только начиная с Kotlin 1.1. Kotlin 1.0 использует только операцию mod
, которая отмечена как устаревшая в Kotlin 1.1.
Ниже приведен пример класса Counter, начинающего счёт с заданного значения, которое может быть увеличено с помощью перегруженного оператора +
.
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
Выражение | Транслируется в |
---|---|
a in b |
b.contains(a) |
a !in b |
!b.contains(a) |
Для операций in
и !in
используется одна и та же процедура, только возвращаемый результат инвертируется.
Выражение | Транслируется в |
---|---|
a[i] |
a.get(i) |
a[i, j] |
a.get(i, j) |
a[i_1, ..., i_n] |
a.get(i_1, ..., i_n) |
a[i] = b |
a.set(i, b) |
a[i, j] = b |
a.set(i, j, b) |
a[i_1, ..., i_n] = b |
a.set(i_1, ..., i_n, b) |
Квадратные скобки транслируются в вызов get
или set
с соответствующим числом аргументов.
Выражение | Транслируется в |
---|---|
a() |
a.invoke() |
a(i) |
a.invoke(i) |
a(i, j) |
a.invoke(i, j) |
a(i_1, ..., i_n) |
a.invoke(i_1, ..., i_n) |
Оператор вызова (функции, метода) в круглых скобках транслируется в invoke
с соответствующим числом аргументов.
Выражение | Транслируется в |
---|---|
a += b |
a.plusAssign(b) |
a -= b |
a.minusAssign(b) |
a *= b |
a.timesAssign(b) |
a /= b |
a.divAssign(b) |
a %= b |
a.modAssign(b) |
Для присваивающих операций, таких как a += b
, компилятор осуществляет следующие шаги:
- Если функция из правой колонки таблицы доступна
- Если соответствующая бинарная функция (т.е.
plus()
дляplusAssign()
) также доступна, то фиксируется ошибка (неоднозначность). - Проверяется, что возвращаемое значение функции
Unit
, в противном случае фиксируется ошибка. - Генерируется код для
a.plusAssign(b)
- Если соответствующая бинарная функция (т.е.
- В противном случае делается попытка сгенерировать код для
a = a + b
(при этом включается проверка типов: тип выраженияa + b
должен быть подтипомa
).
Отметим: присвоение НЕ ЯВЛЯЕТСЯ выражением в Kotlin.
Выражение | Транслируется в |
---|---|
a == b |
a?.equals(b) ?: (b === null) |
a != b |
!(a?.equals(b) ?: (b === null)) |
Отметим: операции ===
и !==
(проверка идентичности) являются неперегружаемыми, поэтому не приводятся никакие соглашения для них.
Операция ==
имеет специальный смысл: она транслируется в составное выражение, в котором экранируются значения null
.
null == null
- это всегда истина, а x == null
для ненулевых x
- всегда ложь, и не должно расширяться в x.equals()
.
Выражение | Транслируется в |
---|---|
a > b |
a.compareTo(b) > 0 |
a < b |
a.compareTo(b) < 0 |
a >= b |
a.compareTo(b) >= 0 |
a <= b |
a.compareTo(b) <= 0 |
Все сравнения транслируются в вызовы compareTo
, от которых требуется возврат значения типа Int
.
Мы можем моделировать вручную инфиксные операции использованием infix function calls.