scala - 为什么我可以在scala中更新对象扩展不可变特征的状态
问题描述
我创建了一个类扩展 scala.Immutable
class SomeThing(var string: String) extends Immutable {
override def toString: String = string
}
正如我所料,scala 编译器应该帮助我防止更改类 SomeThing 的状态。但是当我运行这个测试时
"Test change state of immutable interface" should "not allow" in {
val someThing = new SomeThing("hello")
someThing.string = "hello 1"
println(someThing)
}
结果是hello 1
scala 编译器不会抛出任何警告或错误。
为什么他们必须添加不可变特征而不帮助我们防止对象可变?
解决方案
这个问题有几个方面。
1.一个简单的原因是 Scala 编译器无法真正确保不变性,原因有很多。例如,主要目标平台 JVM 允许final
使用反射修改偶数字段。这不可执行的另一个原因是这样的代码
/////////////////////////////////////////
//// library v1
package library
class LibraryData(val value:Int)
/////////////////////////////////////////
//// code that uses the library
package app
class UserData(val data:LibraryData) extends Immutable
/////////////////////////////////////////
//// library v2
package library
class LibraryData(var value:Int) //now change it to var!
由于“库”是独立于“应用程序”编译的,甚至不知道“应用程序”的存在,因此编译器没有时间点可以捕捉到损坏的合同。
2.你似乎有更根本的误解是什么trait
。在这种情况下trait
(或interface
某些其他语言中的“ ”)表示实现和用户代码之间关于实现如何以及应该如何表现的契约。然而,并非每种合约都可以表示为trait
(至少不会使代码变得超级复杂)。例如,对于可变集合,有一个合约应该返回被调用size
的次数add
(或),但是除了声明有方法和+=
trait
size
+=
带有相应的签名。另一方面,对于大多数合同来说,没有办法强制执行以遵循合同。例如,size
始终返回0
技术上匹配所有类型但显然违反合同的实现。
同样Immutable
的文档说:
所有不可变数据结构(例如不可变集合)的标记特征。
所以它只是一个标记trait
,它是解决不能真正表示为类型的合同的一种方法。它说实现该特征的任何人都声称自己是不可变对象。您的代码声称但显然违反了合同。所以从技术上讲,不遵守合同是你的错。