【问题标题】:Best practice: "If not immutable create copy"-pattern最佳实践:“如果不是不可变的,则创建副本”-模式
【发布时间】:2012-12-27 10:50:03
【问题描述】:

我有一个函数,它获取一个Seq[_] 作为参数并返回一个不可变的类实例,这个Seq 作为一个 val 成员。如果Seq 是可变的,我显然想创建一个防御性副本以保证我的返回类实例不能被修改。

这种模式的最佳实践是什么?首先我很惊讶不可能重载函数

  def fnc(arg: immutable.Seq[_]) = ...
  def fnc(arg: mutable.Seq[_]) = ...

我也可以进行模式匹配:

  def fnc(arg: Seq[_]) = arg match {
    case s: immutable.Seq[_] => { println("immutable"); s}
    case s: mutable.Seq[_] => {println("mutable"); List()++s }
    case _: ?
  }   

但我不确定_ 的情况。是否保证argimmutable.Seqmutable.Seq?我也不知道List()++s 是否是正确的转换方式。我在 SO 上看到了很多帖子,但其中大多数是针对 2.8 或更早版本的。

Scala-Collections 是否足够“智能”,以至于我总是可以(不使用模式匹配)编写 List()++s,如果不可变,我会得到相同的实例,如果可变,我会得到深拷贝?

推荐的方法是什么?

【问题讨论】:

    标签: scala immutability scala-collections


    【解决方案1】:

    如果您想同时支持两者,则需要进行模式匹配。 Seq() ++ 的代码保证(作为其 API 的一部分)如果它是不可变的,它不会复制其余部分:

    scala> val v = Vector(1,2,3)
    v: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)
    
    scala> Seq() ++ v
    res1: Seq[Int] = List(1, 2, 3)
    

    它可能会在某些特殊情况下进行模式匹配,但您知道自己想要的情况。所以:

    def fnc[A](arg: Seq[A]): Seq[A] = arg match {
      case s: collection.immutable.Seq[_] => arg
      case _ => Seq[A]() ++ arg
    }
    

    您不必担心_;这只是说你并不关心类型参数是什么(不是你可以检查),如果你这样写,你不会:如果不可变则通过,否则复制。

    【讨论】:

      【解决方案2】:

      这种模式的最佳实践是什么?

      如果你想保证不变性,最好的做法是做一个防御性的副本,或者要求immutable.Seq

      但我不确定 _ 的情况。保证arg是immutable.Seq还是mutable.Seq?

      不一定,但我相信从collection.Seq 继承的每个标准库 集合也继承自这两者之一。然而,自定义集合理论上可以仅继承自 collection.Seq。请参阅 Rex 的答案以改进您的模式匹配解决方案。

      Scala-Collections 是否足够“智能”,以至于我可以总是(不使用模式匹配)编写 List()++s,如果不可变,我会得到相同的实例,如果可变,我会得到深拷贝?

      它们似乎在某些种情况下,而不是其他情况下,例如:

      val immutableSeq = Seq[Int](0, 1, 2)
      println((Seq() ++ immutableSeq) eq immutableSeq) // prints true
      val mutableSeq = mutable.Seq[Int](0, 1, 2)
      println((Seq() ++ mutableSeq) eq mutableSeq) // prints false
      

      eq 是引用相等。请注意,上述内容也适用于 List() ++ s,但正如 Rex 指出的那样,它不适用于所有集合,例如 Vector

      【讨论】:

        【解决方案3】:

        你当然可以用这种方式超载!例如,这编译得很好:

        object  MIO
        {
          import collection.mutable
        
          def f1[A](s: Seq[A]) = 23
          def f1[A](s: mutable.Seq[A]) = 42
        
          def f2(s: Seq[_]) = 19
          def f2(s: mutable.Seq[_]) = 37
        }
        

        在 REPL 中:

        Welcome to Scala version 2.10.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_37).
        Type in expressions to have them evaluated.
        Type :help for more information.
        
        scala> import rrs.scribble.MIO._; import collection.mutable.Buffer
        import rrs.scribble.MIO._
        import collection.mutable.Buffer
        
        scala> f1(List(1, 2, 3))
        res0: Int = 23
        
        scala> f1(Buffer(1, 2, 3))
        res1: Int = 42
        
        scala> f2(List(1, 2, 3))
        res2: Int = 19
        
        scala> f2(Buffer(1, 2, 3))
        res3: Int = 37
        

        【讨论】:

        • 但是,对于想要防御性复制的情况,您仍然需要在f2(Seq[_]] 中进行模式匹配。例如在val v : Seq[T] = Buffer(t1, t2, t3); 之后,调用f2(v) 调用f2(Seq[_]),而不是f2(mutable.Seq[_]),因为调用解析是在编译时执行的,基于参数的声明类型,而不是在运行时基于参数的运行时类型。换句话说,Scala(如 Java)使用静态而不是多重分派。当需要多次分派时,如这里,我们求助于 Scala 中的模式匹配,或 Java 中的instanceof
        猜你喜欢
        • 1970-01-01
        • 2013-02-13
        • 1970-01-01
        • 1970-01-01
        • 2019-10-22
        • 2013-07-07
        • 1970-01-01
        • 2016-10-20
        • 2016-02-02
        相关资源
        最近更新 更多