可变变量与可变集合

时间:2013-07-23 13:24:44

标签: scala functional-programming

在我的应用程序中,我需要能够交换集合中的元素。所以我有一个选择:

  1. 使用声明为val
  2. 的可变集合
  3. 或使用声明为var的不可变集合(并始终将新集合重新分配给var
  4. 但是在Scala中(在函数式编程中),可变性始终可以避免。那么更糟糕的是:使用val声明的可变集合或声明为var的不可变集合?

6 个答案:

答案 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. 性能要求您使用一个 - 使用它
  2. 方法中的局部范围 - 使用可变集合
  3. 与其他线程/类共享 - 使用不可变集合
  4. 在类中实现,简单代码 - 使用可变集合
  5. 在类中实现,复杂的代码 - 使用不可变的
  6. 但是你应该坚持这样做,因为你坚持使用它。

答案 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