重载 "Number Classes" 的 + 和 += 运算符

overloading + and += operators for "Number Classes"

我想为 类 创建封装简单 Number 的扩展函数。例如 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 对象,而不是初始对象。

有没有办法解决这个限制?

如果 DoubleProperty 是您的 class,您可以将 plusplusAssign 作为它的方法,这应该可以解决任何歧义。

您不能同时重载 ++=。超载其中之一。

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.

我 copied/pasted 来自 Kotlin in Action 一书!

Kotlin 中奇怪的 += 运算符

你可以在kotlin中同时重载plus运算符和plusAssign运算符,但是你必须遵循kotlin的规则来解决奇怪的+=冲突。

  1. introduce an immutable structure of the class for the plus operator which means any class outside the class can't edit its internal data.

  2. introduce a mutable structure of the class for the plusAssign operator which means its internal data can be edited anywhere.

kotlin 已经在 stdlib 中为 CollectionMap 类、Collection#plus and 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]

另一方面,你可以用不同的签名重载一个operator,每个签名对应不同的context,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+ba+=b 在此实现中并不相同。

解决以上两个问题,我的建议是

operator fun SimpleDoubleProperty.plus(number: Number): SimpleDoubleProperty
        = SimpleDoubleProperty(get() + number.toDouble())

因此您无需覆盖 plusAssign。该解决方案不像你的那样通用,但如果你只有 SimpleDoubleProperty 计算,它是正确的,我相信你这样做,因为在你的实现中,plus 总是 returns a SimpleDoubleProperty实例。