Scala集合标准实践

时间:2009-03-23 21:52:47

标签: java scala scala-collections

来自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)?
  4. 我读过Daniel Spiewak的excellent series on scala collections,我仍然对如何在实践中使用它们感到困惑。由于强制完整的包声明,以下内容似乎有点笨拙:

    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包装器下面改变)< / p>

4 个答案:

答案 0 :(得分:26)

  

为什么在包scala中定义List和Seq而不是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写成def init() { mySet = Set("Hello", "World")}并保存所有麻烦或更好但只是把它放在构造函数var mySet : ISet[String] = Set("Hello", "World")

答案 1 :(得分:7)

可变集合偶尔会有用(尽管我同意你应该先看看不可变集合)。如果使用它们,我倾向于写

import scala.collection.mutable

位于文件顶部,(例如):

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

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

  1. 我不想破坏名字,所以我正在使用的类更清楚,
  2. 我很少使用'mutable',因为我的源代码中的“mutable.ClassName”不是 过度的负担。
  3. (另外,我也可以回应'避免空值'评论。它使代码更加健壮和易于理解。我发现我甚至不必像你期望的那样使用Option。)< / p>

答案 2 :(得分:4)

一些随意的想法:

  1. 我从不使用null,我使用Option,这会丢掉一个不错的错误。这种做法已经摆脱了 NullPointerException机会,并迫使人们写出不错的错误。
  2. 除非你确实需要,否则尽量避免查看“可变”内容。
  3. 所以,我对你的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" ) )
      }
    }
    

    我最近在办公室里一直在流泪。 “null是邪恶的!”

答案 3 :(得分:3)

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

在Scala网站上查看此文章:http://www.scala-lang.org/node/2060

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

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

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