2013-07-23 88 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