在我的应用程序中,我需要能够交换集合中的元素。所以我有一个选择:
val
var
的不可变集合(并始终将新集合重新分配给var
)但是在Scala中(在函数式编程中),可变性始终可以避免。那么更糟糕的是:使用val
声明的可变集合或声明为var
的不可变集合?
答案 0 :(得分:10)
这实际上取决于您是否需要广泛分享该系列。可变集合的优点是它通常比不可变集合更快,并且更容易让单个对象传递而不必确保您可以从不同的上下文设置var。但是既然他们可以从你身下改变,你甚至可以在单线程环境中小心:
import collection.mutable.ArrayBuffer
val a = ArrayBuffer(1,2,3,4)
val i = a.iterator
i.hasNext // True
a.reduceToSize(0)
i.next // Boom!
java.lang.IndexOutOfBoundsException: 0
at scala.collection.mutable.ResizableArray$class.apply(ResizableArray.scala:43)
...
因此,如果它被广泛使用,你应该考虑是否可以适当小心避免这样的问题。将var
用于不可变集合通常更安全;然后你可能会过时,但至少你不会因为段错误而摔倒在地。
var v = Vector(1,2,3,4)
val i = v.iterator
i.hasNext // True
v = Vector[Int]()
i.next // 1
但是,现在,您必须将v
作为返回值从任何可能修改它的方法(至少包含它的类之外)传递。如果您忘记更新原始值,这也可能会导致问题:
var v = Vector(1,2,3,4)
def timesTwo = v.map(_ * 2)
timesTwo
v // Wait, it's still 1,2,3,4?!
但是这也没有更新,是吗?:
a.map(_ * 2) // Map doesn't update, it produces a new copy!
所以,作为一般规则,
但是你应该坚持这样做,因为你坚持使用它。
答案 1 :(得分:4)
如果您使用持有不可变集合的var
,您可以相当自由地发布它(尽管您可能希望将var
标记为@volatile
)。在任何给定时间,其他代码只能获得该状态的特定快照,该快照永远不会改变。
如果你使用val
持有一个可变集合实例,那么你必须小心保护它,因为它在更新时可以在不一致的状态下见证。
答案 2 :(得分:1)
可变性是你最好留在笼子里的野兽。理想情况下,在经过充分测试和高度使用的类型的笼子像scala可变集合是。
答案 3 :(得分:1)
我使用的一种安全方法是这样的。首先隐藏你的var以使其线程安全如下:
private[this] val myList: scala.collection.mutable.(whatever)
private [this]不仅将变量限制在此类中,而且仅限于此精确实例。没有其他变量可以访问它。
接下来,创建一个帮助函数,以便在外部需要使用它时返回它的不可变版本:
def myListSafe = myList.toList (or some other safe conversion function like toMap, etc)
您可能会在进行这些转换时获得一些开销,但它使您可以灵活地在类中安全地使用可变集合 - 同时提供在需要时导出线程安全的机会。正如您所指出的,另一种选择是持续改变指向不可变集合的var。
答案 4 :(得分:0)
对var
的重新分配可能难以阅读/维护,例如,如果这种情况发生在方法中的多个位置,则会发生。
因此,使用val
,您至少可以获得一个不可变的引用。
一般来说,可变集合的范围应尽可能小。因此,在完全填充可变集合之后(如果是短暂的缓冲区),您可以将其转换为不可变的集合,例如,作为方法的结果返回它。
正如dbyrne指出的那样:
如果无法将可变集合转换为不可变集合(例如,如果您正在实现缓存),并且可变集合是公共API的一部分,那么var
可能会更好。这种情况下的另一个选择是在实现公共API的方法中创建不可变副本。
答案 5 :(得分:0)
与Rex达成一致,最大的担忧是你是否在分享这个系列。在那种情况下使用不可变。另一种选择:
@volatile
private var myList = immutable.List.empty[Foo]
def theList = myList