| type | doc |
|---|---|
| layout | reference |
| title | Перегрузка операторов |
| category | Syntax |
| url | https://kotlinlang.ru/docs/reference/operator-overloading.html |
Язык 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.