创建(或复制)对象的成本有多高?

时间:2013-12-17 21:19:46

标签: scala

考虑这个简单的例子:

trait A { def a: String = "a"; def a2: String }
case class C(b: String, c: String) extends A {
  val a2 = "a2"
}
val c = C("b", "")
val copy = c.copy(c="c")

当我使用c更新字段.copy(c="c")时,是否复制了其他字段(aa2b)?事件虽然只复制了他们的引用,如果我有一个庞大的层次结构树,.copy会变得非常昂贵吗?

类似地:

class Foo {
  val list = List(1,2,3,4,5,6)
}
val foo1 = new Foo
val foo2 = new Foo

foo1foo2是否共享List的实例,或每当我实例化Foo时,它会创建新的List?如果listvar而不是val,该怎么办?

2 个答案:

答案 0 :(得分:2)

通常Scala是不可变的,您通常必须自己处理可变案例。案例类本质上也是不可变的,它们的复制方法是由编译器生成的。所以是的,他们将共享相同的对象引用。这是不变性很好的原因之一。

你的第二个问题有点不同。在这种情况下,这些类在单独的上下文中一个接一个地构建。

检查正在编译的内容也是一个好主意:

>scalac -print test.scala

[[syntax trees at end of cleanup]] // test.scala
package test {
    class Foo extends Object {
        private[this] val list: List = _;
        <stable> <accessor> def list(): List = Foo.this.list;
        def <init>(): b.Foo = {
            Foo.super.<init>();
            Foo.this.list = immutable.this.List.apply(scala.this.Predef.wrapIntArray(Array[Int]{1, 2, 3, 4, 5, 6}));
            ()
        }
    }
}

从这里我们可以看到Scala每次创建一个新列表。将此更改为var不会改变任何内容,我们可以检查:

>scalac -print test.scala
[[syntax trees at end of cleanup]] // test.scala
package test {
    class Foo extends Object {
        private[this] var list: List = _;
        <accessor> def list(): List = Foo.this.list;
        <accessor> def list_=(x$1: List): Unit = Foo.this.list = x$1;
        def <init>(): b.Foo = {
            Foo.super.<init>();
            Foo.this.list = immutable.this.List.apply(scala.this.Predef.wrapIntArray(Array[Int]{1, 2, 3, 4, 5, 6}));
            ()
        }
    }
}

它只为它生成了一个setter方法(def list_=(x$1: List))。

如果您想在两种情况下引用相同的列表,请使用对象的默认列表对其进行初始化:

object Foo {
    val DefaultList = List(1,2,3,4,5,6)
}

class Foo {
    var list = Foo.DefaultList
}

编译如下:

>scalac -print test.scala
[[syntax trees at end of cleanup]] // test.scala
package test {
    object Foo extends Object {
        private[this] val DefaultList: List = _;
        <stable> <accessor> def DefaultList(): List = Foo.this.DefaultList;
        def <init>(): test.Foo.type = {
            Foo.super.<init>();
            Foo.this.DefaultList = immutable.this.List.apply(scala.this.Predef.wrapIntArray(Array[Int]{1, 2, 3, 4, 5, 6}));
            ()
        }
    };
    class Foo extends Object {
        private[this] var list: List = _;
        <accessor> def list(): List = Foo.this.list;
        <accessor> def list_=(x$1: List): Unit = Foo.this.list = x$1;
        def <init>(): test.Foo = {
            Foo.super.<init>();
            Foo.this.list = Foo.DefaultList();
            ()
        }
    }
}

正如您所看到的,列表仅创建一次,然后通过在每个Foo类实例化上分配的def DefaultList(): List进行引用。

答案 1 :(得分:1)

回答我自己的第一个问题:

特征中的具体val实现为Java静态方法:

// scala
trait A { def a: String = "a"; }

// java
public static java.lang.String a(A);
Code:
   0: ldc           #8                  // String a
   2: areturn  

当然,无法复制静态方法。所以我不需要担心庞大的层次结构树。

抽象val实现为硬编码常量:

// scala
trait B { def b: String }
case class C() { def b = "b" }

// java
public java.lang.String b();
Code:
   0: ldc           #47                 // String b
   2: areturn   

它也很好。

当我调用.copy()时,将复制的唯一内容是构造函数参数列表中的字段。

相关问题