9

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

  1. 使用声明为的可变集合val
  2. 或使用声明为的不可变集合var(并始终将新集合重新分配给var

但是在 Scala 中(在函数式编程中)总是避免可变性。那么更糟糕的是:使用声明的可变集合val或声明为的不可变集合var

4

6 回答 6

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将 a用于不可变集合通常更安全;那么你可能会过时,但至少你不会因为段错误而跌倒在你的脸上。

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. 类中的实现,复杂的代码——使用不可变的

但是你应该尽可能经常地违反它,只要你坚持它。

于 2013-07-23T14:26:50.250 回答
4

如果您使用var持有一个不可变集合,您可以相当自由地发布它(尽管您可能希望将其标记var@volatile)。在任何给定时间,其他代码只能获得该状态的特定快照,并且永远不会改变。

如果您使用val持有一个可变集合实例,那么您必须小心保护它,因为它可能会在更新时处于不一致的状态。

于 2013-07-23T13:54:29.717 回答
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 。

于 2013-07-23T14:30:16.877 回答
1

可变性是你最好关在笼子里的野兽。理想情况下,在经过良好测试和高度使用的笼子类型中,例如 scala mutable 集合。

于 2013-07-23T13:34:06.467 回答
0

同意 Rex,最大的担忧是您是否共享收藏。在这种情况下使用不可变的。另一种选择:

@volatile
private var myList = immutable.List.empty[Foo]
def theList = myList
于 2013-07-23T17:14:18.153 回答
0

对 a 的重新分配var可能难以阅读/维护,尤其是如果这种情况发生在方法中的多个位置,例如。

因此,使用 a val,您至少会获得一个不可变的引用

一般来说,可变集合的范围应该尽可能小。因此,在您完全填充了可变集合之后(如果是短期缓冲区),您可以将其转换为不可变集合,例如将其作为方法的结果返回。

正如 dbyrne 正确指出的那样:如果不可能将可变集合转换为不可变集合(例如,如果您正在实现缓存),并且可变集合是公共 API 的一部分,那么 avar可能会更好。在这种情况下,另一种选择是在实现公共 API 的方法中创建不可变副本。

于 2013-07-23T13:50:41.897 回答