Scala 可变变量与可变集合

Scala 可变变量与可变集合,scala,functional-programming,Scala,Functional Programming,在我的应用程序中,我需要能够交换集合中的元素。所以我有一个选择: 使用声明为val 或者使用声明为var的不可变集合(并始终将新集合重新分配给var) 但在Scala(函数式编程)中,总是避免了可变性。因此,更糟糕的是:使用val声明的可变集合或声明为var的不可变集合?可变性是最好放在笼子里的野兽。理想情况下,在经过良好测试和高度使用的类似于框架的scala可变集合类型中 对var的重新分配可能很难读取/维护,尤其是在一个方法中的多个位置发生这种情况时 因此,使用val,您至少可以得到一个不可

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

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

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

    可变性是最好放在笼子里的野兽。理想情况下,在经过良好测试和高度使用的类似于框架的scala可变集合类型中

    var
    的重新分配可能很难读取/维护,尤其是在一个方法中的多个位置发生这种情况时

    因此,使用
    val
    ,您至少可以得到一个不可变的引用

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

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

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


    如果使用保存可变集合实例的
    val
    ,则必须小心保护它,因为它在更新时可能处于不一致的状态。

    这实际上取决于是否需要广泛共享该集合。可变集合的优点是,它通常比不可变集合快,并且更容易传递单个对象,而不必确保可以从不同的上下文设置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!
    
    所以一般来说,

  • 性能要求您使用一个--使用它
  • 方法中的局部作用域--使用可变集合
  • 与其他线程/类共享--使用不可变集合
  • 类内的实现,简单代码——使用可变集合
  • 类内的实现,复杂代码--使用不可变

  • 但只要你坚持,你就应该经常违反它。

    我使用的一种安全方法就是这样的。。首先隐藏您的var以使其线程安全,如下所示:

    private[this] val myList: scala.collection.mutable.(whatever)
    
    private[this]不仅将变量限制在此类,而且仅限于此实例。没有其他变量可以访问它

    接下来,创建一个helper函数,以便在外部需要使用它时返回它的不可变版本:

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

    执行这些转换可能会带来一些开销,但它为您提供了在类中安全使用可变集合的灵活性,同时提供了在需要时以线程安全方式导出该集合的机会。正如您所指出的,另一种选择是不断地变异指向不可变集合的var

    同意雷克斯的观点,最大的问题是你是否在分享收藏。在这种情况下,请使用immutable。另一种选择:

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

    如果没有更多的背景,这真的没有什么区别。两者都不是功能性方法。可变集合可能会比不可变变量执行得更好。@dbyrne可变集合可能会比不可变变量执行得更好?那两个人不是在同一个队吗?e、 g.
    val misterMutableCollection=collection.mutable…
    @om nom nom使用不可变的var,您必须构造一个全新的集合并交换它。对于可变val,您可以使用原始集合和调用方法对其进行变异。在我看来,这是迄今为止最好的答案,但在某些情况下,集合本身不可变更为重要,而引用并不重要。如果您是库作者,并且希望确保使用您的API的任何人在您将集合返回给他们后不会对其进行变异,则可以使用不可变的var。您是否可以分享更多有关@volatile在Scala中如何工作的信息?据我所知,它标志着多个线程可能在同一时间访问它,但我不确定这个注释到底对变量做了什么改变或做了什么?thanks@volatile其工作原理与java中的volatile关键字完全相同。它在VM级别工作。例如,见。