Scala案例类使用浅拷贝还是深拷贝?

10
case class Person(var firstname: String, lastname: String)

val p1 = Person("amit", "shah")
val p2 = p1.copy()
p1.firstname = "raghu"
p1
p2

p1 == p2

我阅读了一些文档,其中提到Scala案例类的复制方法使用浅拷贝。

但是,我无法理解以下示例的输出:

我创建了一个名为p1的人,然后从p1创建了一个名为p2的副本,并将p1.firstname更改为“raghu”。

因此,在浅拷贝的情况下,p2.firstname的值应该更改,但这里并没有发生。

参考:https://docs.scala-lang.org/tour/case-classes.html

2个回答

13

你的疑惑在于“变量”和“值”的区别。

因此,当你执行以下操作时,

val p1 = Person("amit", "shah")
val p2 = p1.copy()

那么p2p1的浅拷贝,因此变量p1.firstnamep2.firstname指向相同的String类型的value,即"amit"

当您执行p1.firstname = "raghu"时,实际上是告诉变量p1.firstname指向不同的String类型的value,该值为"raghu"。在这里,您并没有改变value本身,而是改变了variable

如果要更改value本身,则p1p2都将反映出更改。不幸的是,在Scala中,String值是不可变的,因此您无法修改String值。

让我通过使用可修改的东西,例如ArrayBuffer来展示给您。

scala> import scala.collection.mutable.ArrayBuffer
// import scala.collection.mutable.ArrayBuffer

scala> case class A(s: String, l: ArrayBuffer[Int])
// defined class A

scala> val a1 = A("well", ArrayBuffer(1, 2, 3, 4))
// a1: A = A(well,ArrayBuffer(1, 2, 3, 4))

scala> val a2 = a1.copy()
// a2: A = A(well,ArrayBuffer(1, 2, 3, 4))

// Lets modify the `value` pointed by `a1.l` by removing the element at index 1
scala> a1.l.remove(1)
// res0: Int = 2

// You will see the impact in both a1 and a2.

scala> a1
// res1: A = A(well,ArrayBuffer(1, 3, 4))

scala> a2
//res2: A = A(well,ArrayBuffer(1, 3, 4))

12
你可以将String变量的值想象成对某个值存储在Value Store中的字符串的引用。enter image description here 使用浅拷贝时,所有值仍然指向它们原始的值,没有创建“第二个字符串”。
然而,由于JVM将字符串引用视为值,因此当分配firstname时,它现在指向“raghu”。
如果我们将字符串包装在另一个类中,让我们称其为case class Box(var s:String) 那么JVM(因此Scala)将使用对橙色“盒子”的引用,而不是字符串。 case class Person(var firstname: Box, lastname: Box)
val p1 = Person(Box("amit"), Box("shah"))
val p2 = p1.copy()
p1.firstname = Box("raghu")

相同的图形适用,因为它是一个拷贝。

所有的引用都是副本,现在指向橙色的框。

如果不是将引用更改为新的框,而是更改框内的字符串。

p1.firstname.s = "raghu",你所做的是替换框内的值。

enter image description here

如果有一种理论上的“深拷贝”方法。 enter image description here 它会复制引用、盒子和内部字符串。

在JVM上,字符串的处理很奇怪。它们有时像值一样操作,有时像单例值,而它们的引用相等性(在Java中)会因此变得混乱。但这是一个实现细节,对Java和Scala都隐藏了起来。因此,我们可以将字符串视为值(详见什么是Java字符串池?,但现在可能过于高级)。另外,请参考Scala论坛:https://www.scala-lang.org/old/node/10049.html


很棒的图形表示。您可能需要稍微改变您的回答语言,例如“String类型是对String值的引用”这样的句子会产生误导。 - sarveshseri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接