问题
I want to create extension functions for classes that encapsulate simple Number
s. For example DoubleProperty
. I encountered the problem, that I can't overload the +
and the +=
operator at the same time.
I wan't to create a behaviour, that passes following tests:
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)
}
}
It could be implemented using these extension functions:
operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
operator fun WritableDoubleValue.plusAssign(number: Number)
= set(get() + number.toDouble())
The problem is, that if +
is overlodaded the +=
can't be overloaded aswell:
Assignment operators ambiguity. All these functions match.
- public operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
- public operator fun WritableDoubleValue.plusAssign(number: Number): Unit
If I only overload the +
operator, a new DoubleProperty
object is returned on +=
operations instead of the initial one.
Is there a way to work around this limitation?
回答1:
The strange +=
operator in Kotlin
you can both overloading the plus
operator and plusAssign
operator in kotlin, but you must following the rules of kotlin to solving the strange +=
conflicts.
introduce an immutable structure of the class for the
plus
operator which means any class outside the class can't edit its internal data.introduce a mutable structure of the class for the
plusAssign
operator which means its internal data can be edited anywhere.
the kotlin has already done such things in the stdlib
for the Collection
& the Map
classes, the Collection#plus and MutableCollection#plusAssign as below:
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
But wait, how to solving the conflict when we using the +=
operator?
IF the list is an immutable Collection
then you must define a mutable var
variable, then the plus
operator is used since its internal state can't be edited. for example:
// 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]
IF the list is a mutable MutableCollection
then you must define a immutable val
variable, then the plusAssign
operator is used since its internal state can be edited anywhere. for example:
// 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]
On the other hand, you can overloads an operator with diff signatures, each signature for the different context, and kotlin also do it, e.g: Collection#plus. for example:
var list = listOf<Int>();
list += 1; //list = [1];
// ^--- list = list.plus(Integer);
list += [2,3]; //list = [1, 2, 3]
// ^--- list = list.plus(Iterable);
回答2:
Your operator override implementation has two problems:
1. inconsistent type after plus
operator fun ObservableDoubleValue.plus(number: Number): DoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
Any ObservableDoubleValue
instance plus a Number
, got a DoubleProperty
instance(or say a SimpleDoubleProperty
instance). Let's say I have a type ComplexDoubleProperty
implements ObservableDoubleValue
, you will see:
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
You can see this behavior makes no sense.
2. a=a+b and a+=b should be identical
If your implementation compiles, you will have
var a: DoubleProperty = SimpleDoubleProperty(0.1) //add DoubleProperty to make it compile
var b = a
a += 0.1
println(b == a)
prints true
because +=
sets the value to the original instance. If you replace a+=0.1
with a=a+0.1
you will get false
because a new instance is returned. Generally speaking, a=a+b
and a+=b
are not identical in this implementation.
To fix the two problems above, my suggestion is
operator fun SimpleDoubleProperty.plus(number: Number): SimpleDoubleProperty
= SimpleDoubleProperty(get() + number.toDouble())
so you don't need to override plusAssign
. The solution is not as general as yours, but it's correct if you only have SimpleDoubleProperty
calculations, and I believe you do, because in your implementation, plus
always returns a SimpleDoubleProperty
instance.
回答3:
You cannot overload both +
and +=
. Overload one of the them.
When you write += in your code, theoretically both plus the plusAssign functions can be called (see figure 7.2). If this is the case, and both functions are defined and applicable, the compiler reports an error.
I copied/pasted from Kotlin in Action book!
回答4:
If DoubleProperty
is your class, you can make plus
and plusAssign
its methods, that should resolve any ambiguity.
来源:https://stackoverflow.com/questions/44558663/overloading-and-operators-for-number-classes