You can imagine the values of String variables, as being references to Strings stored in a Value Store somewhere.
With a shallow copy, all values are still pointed at their original values, there isn't a "second string" created.
However, since the JVM treats string references like values, when firstname is assigned it is now pointing to "raghu"
If we instead wrap the string in another class, let's call it case class Box(var s:String)
Then the JVM (and thus scala) will be using references to orange 'boxes' instead of strings.
case class Person(var firstname: Box, lastname: Box)
val p1 = Person(Box("amit"), Box("shah"))
val p2 = p1.copy()
p1.firstname = Box("raghu")
The same exact graphic applies, because it was a shallow copy.
all the references are copies, and now point to the box, in orange.
if instead of changing the reference to a new box, you change the string inside the box.
p1.firstname.s = "raghu" what you are instead doing is replacing the value inside the box.
If there were some theoretical "deep copy" method.
It would copy the references, the boxes, and the strings inside.
Strings are strange on the JVM. they act like values, and sometimes singleton values, and their reference equality (in java) is messed up because of it, but this is an implementation detail, hidden to both Java and Scala. So we can treat Strings as values. (see What is Java String interning? for more on this, but it may be too advanced for now) and a Scala thread: https://www.scala-lang.org/old/node/10049.html
A String type, is a reference to a String Value
are misleading. – Hispaniola