2013-07-23 91 views
9

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

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

但在斯卡拉(函数式编程)总是可以避免可变性。那么更糟糕的是:使用val声明的可变集合还是声明为var的不可变集合?

+1

没有更多的上下文,它并没有真正有所作为。这两者都不是一种功能性方法。可变集合可能会比不可变var更好。 – dbyrne

+0

@dbyrne *可变集合可能比不可变var * whoa更好?在同一支球队中不是那两个人吗?例如'val misterMutableCollection = collection.mutable ....' –

+1

@ om-nom-nom对于不可变的变量,你必须构造一个全新的集合并将其交换。有了可变的val,你可以使用原始的集合和调用方法来改变它。 – dbyrne

回答

1

可变性是你最好保持在笼子里的野兽。理想情况下,在经过良好测试和高度使用类型的笼子像斯卡拉可变收藏是。

0

重新分配到var可能很难读取/维护,特别是如果这发生在方法中的多个位置。

所以用val,你至少得到一个不可变的参考

一般而言,可变集合的范围应尽可能小。因此,在完全填充可变集合(如果存在短暂缓冲区的情况下)之后,可以将其变为不可变的集合,例如将其作为方法的结果返回。

由于dbyrne指出正确: 如果这是不可能的可变集合转换成不可变的一个(如果您正在实现例如高速缓存),以及可变集合是公共API的一部分,那么var可能会更好。在这种情况下的另一个选项是在实现公共API的方法中创建一个不可变副本。

+1

在我看来,这是迄今为止最好的答案,但在某些情况下,对于集合本身来说更重要的是它是不可变的,而且参考并不重要。如果你是一个图书馆作者,并且你想确保使用你的API的任何人在将它返回给他们之后不会改变它的集合,那么一个不可变的var将是合适的。 – dbyrne

+0

@dbyrne感谢您的评论;我已经更新了答案。 – Beryllium

4

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

如果使用持有可变集合实例的val,则必须小心谨慎,因为它可以在更新时以不一致的状态进行目击。

+0

你能分享一下关于@ volatile在Scala中的工作方式吗?从我的理解中可以看出,多个线程可能同时访问它,但我不确定这个注释是真的改变了还是对变量有影响?谢谢 – LaloInDublin

+1

@volatile与java中的volatile关键字完全相同。它适用于VM级别。例如,请参阅http://stackoverflow.com/questions/2694439/how-does-volatile-actually-work。 –

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

我用一种安全的方法的工作原理是这样的。首先隐藏变种,使其线程安全的是这样的:

private[this] val myList: scala.collection.mutable.(whatever) 

私人[这]限制变量不仅这一类,但只有这个确切的例子。没有其他变量可以访问它。

接下来,做一个辅助函数来返回它的一个不可改变的版本,只要外在的东西需要与它的工作:

def myListSafe = myList.toList (or some other safe conversion function like toMap, etc) 

你可能会得到一些开销做这些转换,但它给你使用的灵活性一个安全的类内可变集合 - 同时提供机会在需要时将其导出为线程安全。正如你已经指出的另一种选择是不断mutate一个var指向一个不可变的集合。

0

与雷克斯达成一致,最大的担忧是你是否分享收藏品。在这种情况下使用不可变的。另一种选择:

@volatile 
private var myList = immutable.List.empty[Foo] 
def theList = myList