10

我想为封装 simple 的类创建扩展函数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对象+=而不是初始对象。

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

4

4 回答 4

11

+=Kotlin 中的奇怪运算符

pluskotlin 中的operator 和operator都可以重载plusAssign,但必须遵循 kotlin 的规则来解决奇怪的+=冲突。

  1. 为操作符引入类的不可变结构plus,这意味着类外的任何类都不能编辑其内部数据。

  2. 为操作符引入类的可变结构plusAssign,这意味着它的内部数据可以在任何地方进行编辑。

kotlin 已经在stdlib&CollectionMapCollection#plusMutableCollection#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 签名重载运算符,每个签名用于不同的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);
于 2017-06-15T08:14:48.583 回答
2

您的运算符覆盖实现有两个问题:

1. 后的类型不一致plus

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

任何ObservableDoubleValue实例加上一个Number,得到一个DoubleProperty实例(或说一个SimpleDoubleProperty实例)。假设我有一个类型ComplexDoublePropertyimplements 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.1a=a+0.1你会得到false因为一个新的实例被返回。一般来说,a=a+ba+=b这个实现中并不完全相同。

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

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

所以你不需要覆盖plusAssign. 该解决方案不像您的那样通用,但是如果您只有SimpleDoubleProperty计算是正确的,我相信您这样做,因为在您的实现中,plus总是返回一个SimpleDoubleProperty实例。

于 2017-06-15T10:58:34.883 回答
1

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

当您在代码中编写 += 时,理论上可以调用 plusAssign 函数(见图 7.2)。如果是这种情况,并且两个函数都已定义且适用,则编译器会报告错误。

我从Kotlin in Action书中复制/粘贴!

于 2017-06-15T07:40:29.363 回答
-2

如果DoubleProperty是您的课程,您可以制作plus及其plusAssign方法,这应该可以解决任何歧义。

于 2017-06-15T06:35:31.817 回答