31

来自Java背景,我习惯于处理集合的常见做法:显然会有例外,但通常代码如下所示:

public class MyClass {
  private Set<String> mySet;

  public void init() {
    Set<String> s = new LinkedHashSet<String>();
    s.add("Hello");
    s.add("World"); 
    mySet = Collections.unmodifiableSet(s);
  }
}

我不得不承认,我对 Scala 中过多的选项感到有些困惑。有:

  • scala.List(和Seq
  • scala.collections.Set(和Map
  • scala.collection.immutable.Set(和MapStack但不是List
  • scala.collection.mutable.Set(和MapBuffer但不是List
  • scala.collection.jcl

所以问题!

  1. 为什么ListSeq定义在包中scala而不是 scala.collection(即使实现Seq在集合子包​​中)?
  2. 初始化集合然后冻结它的标准机制是什么(在 Java 中是通过包装在 中来实现的unmodifiable)?
  3. 为什么某些集合类型(例如MultiMap)只定义为可变的?(没有不可变的MultiMap)?

我已经阅读了 Daniel Spiewak关于 scala 集合的优秀系列,但我仍然对如何在实践中实际使用它们感到困惑。由于强制执行完整的包声明,以下内容似乎有点笨拙:

class MyScala {
  var mySet: scala.collection.Set[String] = null 

  def init(): Unit = {
     val s = scala.collection.mutable.Set.empty[String]
     s + "Hello"
     s + "World"
     mySet = scala.collection.immutable.Set(s : _ *)

  }
}

尽管可以说这比 Java 版本更正确,因为不可变集合不能更改(如在 Java 情况下,可以在unmodifiable包装器下更改底层集合)

4

4 回答 4

26

为什么 List 和 Seq 在包 scala 而不是 scala.collection 中定义(即使 Seq 的实现在集合子包​​中)?

因为它们被认为非常有用,以至于它们会通过 scala.Predef 中的同义词自动导入所有程序。

初始化集合然后冻结它的标准机制是什么(在 Java 中是通过包装不可修改的来实现的)?

Java 没有冻结集合的机制。它只有一个习惯用法,用于将(仍可修改的)集合包装在引发异常的包装器中。Scala 中正确的习惯用法是将可变集合复制到不可变集合中 - 可能使用 :_*

为什么某些集合类型(例如 MultiMap)只定义为可变的?(没有不可变的MultiMap)?

团队/社区还没有到达那里。2.7 分支增加了很多,预计 2.8 会增加更多。

由于强制执行完整的包声明,以下内容似乎有点笨拙:

Scala 允许导入别名,因此在这方面它总是比 Java 更简洁(参见例如 java.util.Date 和 java.sql.Date - 使用这两种方法来完全限定)

import scala.collection.{Set => ISet}
import scala.collection.mutable.{Set => MSet}

class MyScala {
  var mySet: ISet[String] = null 

  def init(): Unit = {
     val s = MSet.empty[String]
     s + "Hello"
     s + "World"
     mySet = Set(s : _ *)
  }
}

当然,你真的只是写 init asdef init() { mySet = Set("Hello", "World")}并省去所有的麻烦,或者更好的是把它放在构造函数中var mySet : ISet[String] = Set("Hello", "World")

于 2009-03-23T22:21:57.713 回答
7

可变集合有时很有用(尽管我同意您应该始终先查看不可变集合)。如果使用它们,我倾向于写

import scala.collection.mutable

在文件的顶部,并且(例如):

val cache = new mutable.HashMap[String, Int]

在我的代码中。这意味着您只需要编写“mutable.HashMap”,而不是 scala.collection.mutable.HashMap”。正如上面的评论员所提到的,您可以在导入中重新映射名称(例如,“import scala.collection.mutable.{HashMap => MMap}”),但是:

  1. 我不想弄乱名称,以便更清楚我正在使用哪些类,并且
  2. 我很少使用'mutable',以至于在我的源代码中包含“mutable.ClassName”并不是一个过度的负担。

(另外,我也可以回应“避免空值”的评论吗?它使代码更加健壮和易于理解。我发现我什至不必像你期望的那样使用 Option。)

于 2009-11-19T12:40:34.010 回答
4

一些随机的想法:

  1. 我从不使用null,我使用Option,然后会抛出一个体面的错误。这种做法已经消除了大量 NullPointerException的机会,并迫使人们写出体面的错误。
  2. 除非你真的需要,否则尽量避免研究“可变”的东西。

因此,我对您的 scala 示例的基本看法是,您必须稍后在其中初始化集合

class MyScala {
  private var lateBoundSet:Option[ Set[ String ] ] = None
  def mySet = lateBoundSet.getOrElse( error("You didn't call init!") )

  def init {
     lateBoundSet = Some( Set( "Hello", "World" ) )
  }
}

我最近在办公室里哭了。“空是邪恶的!”

于 2009-06-05T21:16:32.097 回答
3

请注意,当前版本的 Scala 集合 API 可能存在一些不一致之处;对于 Scala 2.8(将在 2009 年晚些时候发布),集合 API 正在大修以使其更加一致和更加灵活。

请参阅 Scala 网站上的这篇文章:http ://www.scala-lang.org/node/2060

添加到 Tristan Juricek 的带有 lateBoundSet 的示例:Scala 有一个用于延迟初始化的内置机制,使用“lazy”关键字:

class MyClass {
    lazy val mySet = Set("Hello", "World")
}

通过这样做, mySet 将在第一次使用时初始化,而不是在创建新的 MyClass 实例时立即初始化。

于 2009-08-03T11:25:52.917 回答