为“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()) 

问题是,如果+被overlodaded +=不能超载,以及:

 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对象,而不是最初的那个。

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

Kotlin中奇怪的+=运算符

你可以重载plusAssignplus操作符和plusAssign操作符,但是你必须遵循kotlin的规则来解决奇怪的+=冲突。

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

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

kotlin已经在Stdlib中为Collection &the 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] 

另一方面,你可以使用差异签名来重载一个运算符 ,每个签名用于不同的上下文 ,而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=a+0.1代替a+=0.1 ,则会返回false因为返回了新的实例。 一般来说, a=a+ba+=b在这个实现中是不相同的。

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

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

所以你不需要重写plusAssign 。 该解决方案不如您的一般,但是如果您只有SimpleDoubleProperty计算是正确的,并且我相信您这样做,因为在您的实现中,总是返回一个SimpleDoubleProperty实例。

你不能重载++= 。 重载其中之一。

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

我从行动书中复制/粘贴Kotlin

如果DoubleProperty是您的类,则可以使用plusplusAssign其方法,这可以解决任何不明确的问题。