Перегрузка операторов

Kotlin позволяет реализовывать предопределённый набор операторов для ваших типов. Эти операторы имеют фиксированное символическое представление (вроде + или *) и фиксированные приоритеты. Для реализации оператора предоставьте функцию-член или функцию-расширение с фиксированным именем и с соответствующим типом, т. е. левосторонним типом для бинарных операций или типом аргумента для унарных операций.

Функции, которые перегружают операторы, должны быть отмечены модификатором operator.

interface IndexedContainer {
    operator fun get(index: Int)
}

При переопределении перегрузок оператора вы можете опускать operator.

class OrdersList: IndexedContainer {
    override fun get(index: Int) { /*...*/ }   
}

Унарные операторы

Унарные префиксные операторы

Выражение Транслируется в
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()

Под транслированием, понимается представление вашего кода после компиляции.

Эта таблица демонстрирует, что компилятор при обрабатывании, к примеру, выражения +a, осуществляет следующие действия:

  • Определяет тип выражения a, пусть это будет T;
  • Ищет функцию unaryPlus() с модификатором operator без параметров для приёмника типа Т, т. е. функцию-член или функцию-расширение;
  • Если функция отсутствует или неоднозначная, возвращается ошибка компиляции;
  • Если функция присутствует и R - её возвращаемый тип, выражение +a имеет Тип R.

Эти операции, как и все остальные, оптимизированы для основных типов и не требуют дополнительных затрат на вызовы этих функций для них.

Например, вы можете перегрузить оператор унарного минуса:

data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus() = Point(-x, -y)

fun main() {
   println(-point)  // выведет "Point(x=-10, y=-20)"
}

Инкремент и декремент

Выражение Транслируется в
a++ a.inc() + см. ниже
a-- a.dec() + см. ниже

Функции inc() и dec() должны возвращать значение, которое будет присвоено переменной, к которой была применена операция ++ или -- . Они не должны изменять сам объект, для которого были вызваны эти функции.

Компилятор осуществляет следующие шаги, обрабатывая операторы в постфиксной форме, например a++:

  • Определяет тип переменной a, пусть это будет T;
  • Ищет функцию inc() с модификатором operator без параметров, применимую для приёмника типа Т.
  • Проверяет, что возвращаемый тип такой функции является подтипом T.

Результатами вычисления выражения является:

  • Сохранение инициализирующего значения a во временную переменную a0,
  • Сохранение результата выполнения a0.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..b a.rangeTo(b)

Для перечисленных в таблице операций компилятор всего лишь решает выражение из колонки Транслируется в.

Ниже приведен пример класса Counter (счетчик), начинающего счёт с заданного значения, которое может быть увеличено с помощью перегруженного оператора +:

data class Counter(val dayIndex: Int) {
    operator fun plus(increment: Int): Counter {
        return Counter(dayIndex + increment)
    }
}

Оператор in

Выражение Транслируется в
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()) также доступна, a - изменяемая переменная и возвращаемый тип plus является подтипом типа a, то фиксируется ошибка (неоднозначность);
    • Проверяется, что возвращаемое значение функции 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))

Эти операторы работают только с функцией equals(other: Any?): Boolean, которая может быть переопределена для обеспечения пользовательской реализации проверки равенства. Любая другая функция с тем же именем (например, equals(other: Foo)) вызываться не будет.

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

Операция == имеет специальный смысл: она транслируется в составное выражение, в котором экранируются значения null. null == null - это всегда истина, а x == null для non-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.

Операторы делегирования свойств

Операторы provideDelegate, getValue и setValue описаны в Делегированные свойства.

Инфиксные вызовы именованных функций

Вы можете имитировать инфиксные операции, используя инфиксную запись.