【问题标题】:Unmodifiable view of a mutable Scala collection可变 Scala 集合的不可修改视图
【发布时间】:2012-11-21 06:00:07
【问题描述】:

我有一个带有私有字段的类,它是一个可变集合。这个特定实例中的字段是ArrayBuffer,尽管我的问题扩展到任何有限、有序、随机访问的集合类型。我想公开这个字段而不允许其他人修改它。在 Java 中,我会添加如下方法:

private List<T> theList;

public List<T> getList() {
    return Collections.unmodifiableList(theList);
}

在 Java 中,我们只接受结果是 List,它没有完全实现 List 接口,因为 #add 和朋友抛出 UnsupportedOperationException

在 Scala 中,我希望找到一个合适的特征,其中包含 iteratorsizeapply(用于按索引检索值)等访问器,但没有修改器。有没有我还没有发现的这种类型?

【问题讨论】:

  • 是你想要的.toVector 方法吗?它将以Vector 的形式返回ArrayBuffer 中值的副本,该Vector 是不可变的并支持快速随机访问。 (.toVector 仅在 Scala 2.10 中,对于 2.9.2,您可以执行类似 Vector() ++ myArrayBuffer 的操作)
  • 集合大小可能在几万。这足以证明我对不可变集合复制的反感吗?
  • 您的 Java 示例不会生成不可变列表。它只是在不可变的包装器中隐藏了一个可变列表。任何保留对底层列表的引用的对象仍然可以对其进行变异...如果对您来说足够了,您可以轻松实现相同的方法...
  • 是的,我说的是“不可修改”而不是“不可变”,并明确引用了在 java.util.List 上执行此类操作的方法,因为这正是我正在寻找的行为为。

标签: scala scala-collections


【解决方案1】:

Scala 集合库是面向不变性的,不变性不仅仅指你不能修改一个给定的集合,而且保证你的集合永远不会存在被任何人修改

所以你不能也不应该从 Scala 中的可变缓冲区中获取像 immutable.Seq 这样的集合作为视图,因为它违反了这一保证。

但是您可以像这样简单地实现不可修改的可变 Seq 的概念:

class UnmodifiableSeq[A](buffer: mutable.Seq[A]) extends mutable.Seq[A]{
    def update(idx: Int, elem: A) {throw new UnsupportedOperationException()}

    def length = buffer.length

    def apply(idx: Int) = buffer(idx)

    def iterator = buffer.iterator
}

用法:

val xs = Array(1, 2, 3, 4)
val view = new UnmodifiableSeq(xs)
println(view(2)) >> 3
view(2) = 10 >> Exception in thread "main" java.lang.UnsupportedOperationException

编辑:

获得不可修改的集合视图的一种可能更好的方法是向下转换为不提供可变更新操作的collection.Seq

val xs = Array(1, 2, 3)
val view: Seq[Int] = xs //this is unmodifiable

如果您有自己的自定义可变类,则创建一个扩展 Seq 的包装器。

class UnmodifiableView[A](col: MutableCollection[A]) extends collection.Seq[A]{
    def length = col.length

    def apply(idx: Int) = col(idx)

    def iterator = col.iterator
}

scala.collection.Seq trait 不能保证不变性,但它也不允许任何修改操作,因此它看起来非常合适。

【讨论】:

  • 我同意破坏 immutable.Seq 的不变性保证是错误的,但是破坏 mutable.Seq#update(Int,A) 的契约不是也是个坏主意吗?
  • @ChristopherMartin - 是的,我同意。我必须承认我在回答中犯了一个错误,因为我认为 collection.Seq 是 collection.immutable.Seq 的别名。事实证明,事实并非如此。因此,您可以扩展不提供不变性保证的 collection.Seq。我将在我的答案中添加修复。
  • 太完美了,谢谢。我也没想到会存在三个 Seq 特征(collection、collection.immutable、collection.mutable)。不过,Scala 还没有包装类仍然有点令人失望。我想我会直接转换为 collection.Seq 并停止对滥用的可能性如此偏执。
【解决方案2】:

将您的缓冲区转换为Seq,或属于scala.collection 包的其他不可修改集合之一。

myBuffer.toSeq

【讨论】:

  • 好(也许足够好)的建议 - 但它并不能真正确保返回值的不可修改性。一个非常愚蠢的同事可以将 Seq 转换回 ArrayBuffer 并对其进行更改。
  • 我刚试了一下,效果很好... scala> import collection.mutable.ArrayBuffer; val a = new ArrayBuffer[Int]; a.附加(1); val b:Seq[Int] = a.toSeq; b.asInstanceOf[ArrayBuffer[Int]].append(2);
  • 好吧,一个足够愚蠢的同事也可以通过反射获得Collections.unmodifiableList的底层集合并对其进行修改。
  • 这不太可能,你不觉得吗?
【解决方案3】:

如果你想:

  • 仅使用现有的库函数(不要编写自己的包装器)
  • 确保避免复制
  • 确保返回的值不能转换为可修改的值 那么一种可能性是简单地返回一个迭代器

    def getStuff = array.iterator
    

当然,如果您的规范明确要求您返回 Seq,这不是一个选项,但它

  • 允许调用者使用与 Seq (for (x &lt;- obj.getStuff)) 相同的语法对其进行迭代
  • 允许调用者使用obj.getStuff.toSeqobj.getStuff.toList 轻松转换为Seq 或List。

(请注意,.iterator 的文档并没有明确说明返回的对象不能转换为可修改的对象,但 ArrayBuffer.iterator 的当前实现确实给出了不可修改的迭代器。)

【讨论】:

    【解决方案4】:

    如果myBuffer.toSeq 不够好,您可以编写自己的类来扩展scala.collection.IndexedSeqOptimized,将调用委托给底层集合。

    【讨论】:

      猜你喜欢
      • 2012-02-12
      • 1970-01-01
      • 1970-01-01
      • 2012-01-07
      • 2013-08-30
      • 2011-11-24
      • 1970-01-01
      • 2019-10-12
      • 2017-05-21
      相关资源
      最近更新 更多