Перегрузка операторов + и += для "числовых классов"
Я хочу создать функции расширения для классов, которые инкапсулируют простые Number
s. Например DoubleProperty
, Я столкнулся с проблемой, что я не могу перегрузить +
и +=
оператор одновременно.
Я не хочу создавать поведение, которое проходит следующие тесты:
class DoublePropertyTest {
lateinit var doubleProperty: DoubleProperty
@Before
fun initialize() {
doubleProperty = SimpleDoubleProperty(0.1)
}
@Test
fun plus() {
val someProperty = doubleProperty + 1.5
assertEquals(someProperty.value, 1.6, 0.001)
}
@Test
fun plusAssign() {
val someProperty = doubleProperty
doubleProperty += 1.5 //error if + and += are overloaded
assert(someProperty === doubleProperty) //fails with only + overloaded
assertEquals(doubleProperty.value, 1.6, 0.001)
}
}
Это может быть реализовано с использованием этих функций расширения:
operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
operator fun WritableDoubleValue.plusAssign(number: Number)
= set(get() + number.toDouble())
Проблема в том, что если +
перегружен +=
не может быть перегружен, а также:
Assignment operators ambiguity. All these functions match.
- public operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
- public operator fun WritableDoubleValue.plusAssign(number: Number): Unit
Если бы я только перегрузить +
оператор, новый DoubleProperty
объект возвращается +=
операции вместо начальной.
Есть ли способ обойти это ограничение?
4 ответа
Странный +=
оператор в Котлине
вы можете перегружать plus
оператор и plusAssign
оператор в kotlin, но вы должны следовать правилам kotlin для решения странных +=
конфликты.
ввести неизменную структуру класса для
plus
оператор, который означает, что любой класс вне класса не может редактировать свои внутренние данные.ввести изменяемую структуру класса для
plusAssign
оператор, который означает, что его внутренние данные могут быть изменены в любом месте.
Котлин уже сделал такие вещи в stdlib
для Collection
& Map
классы, Collection # plus и MutableCollection # plusAssign, как показано ниже:
operator fun <T> Collection<T>.plus(elements: Iterable<T>): List<T>
// ^--- immutable structure
operator fun <T> MutableCollection<in T>.plusAssign(elements: Iterable<T>)
// ^--- mutable structure
Но подождите, как решить конфликт, когда мы используем +=
оператор?
ЕСЛИ список неизменен Collection
тогда вы должны определить изменчивый var
переменная, то plus
оператор используется, так как его внутреннее состояние не может быть отредактировано. например:
// v--- define `list` with the immutable structure explicitly
var list: List<Int> = arrayListOf(1); //TODO: try change `var` to `val`
val addend = arrayListOf(2);
val snapshot = list;
list += addend;
// ^--- list = list.plus(addend);
// list = [1, 2], snapshot=[1], addend = [2]
Если список изменчив MutableCollection
тогда вы должны определить неизменный val
переменная, то plusAssign
оператор используется, поскольку его внутреннее состояние можно редактировать где угодно. например:
// v--- `list` uses the mutable structure implicitly
val list = arrayListOf(1); //TODO: try change `val` to `var`
val addend = arrayListOf(2);
val snapshot = list;
list += addend;
// ^--- list.plusAssign(addend);
// list = [1, 2], snapshot=[1, 2], addend = [2]
С другой стороны, вы можете перегружать оператора с помощью подписей diff, каждой подписи для своего контекста, и kotlin также делает это, например: Collection # plus. например:
var list = listOf<Int>();
list += 1; //list = [1];
// ^--- list = list.plus(Integer);
list += [2,3]; //list = [1, 2, 3]
// ^--- list = list.plus(Iterable);
У вашей реализации переопределения оператора есть две проблемы:
1. противоречивый тип послеplus
operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
любой ObservableDoubleValue
экземпляр плюс Number
, Получил DoubleProperty
экземпляр (или, скажем, SimpleDoubleProperty
пример). Допустим, у меня есть тип ComplexDoubleProperty
инвентарь ObservableDoubleValue
, ты увидишь:
var a = getComplexDoubleProperty()
a = a + 0.1 //compile error, WTF?
//or even
var b = SimpleDoubleProperty(0.1)
b = b + 0.1 //compile error, because b+0.1 is DoubleProperty
Вы можете видеть, что это поведение не имеет смысла.
2. a = a + b и a+=b должны быть идентичны
Если ваша реализация компилируется, у вас будет
var a: DoubleProperty = SimpleDoubleProperty(0.1) //add DoubleProperty to make it compile
var b = a
a += 0.1
println(b == a)
печать true
так как +=
устанавливает значение в исходный экземпляр. Если вы замените a+=0.1
с a=a+0.1
ты получишь false
потому что новый экземпляр возвращается. Вообще говоря, a=a+b
а также a+=b
не идентичны в этой реализации.
Чтобы исправить две проблемы выше, я предлагаю
operator fun SimpleDoubleProperty.plus(number: Number): SimpleDoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
так что вам не нужно переопределять plusAssign
, Решение не такое общее, как у вас, но оно правильное, если у вас есть только SimpleDoubleProperty
расчеты, и я верю, что вы делаете, потому что в вашей реализации, plus
всегда возвращает SimpleDoubleProperty
пример.
Вы не можете перегружать оба +
а также +=
, Перегрузите один из них.
Когда вы пишете += в своем коде, теоретически могут вызываться обе функции плюс функции PlusAssign (см. Рисунок 7.2). Если это так, и обе функции определены и применимы, компилятор сообщает об ошибке.
Я скопировал / вставил из Kotlin в книгу действий!
Если DoubleProperty
это ваш класс, вы можете сделать plus
а также plusAssign
его методы, которые должны разрешить любую неопределенность.