Java中的深拷贝和”引用”
(本文章假设基于Java SE 8或以上版本)
深拷贝指的是在复制源和目标时,不会对目标/源产生影响的一种拷贝方式。
要在Java中实现引用类型的深拷贝,需要生成一个具有与源相同值的新对象,并且需要进行一些小的改进。
实际的代码示例(列表的情况下)
如果您想要深度复制一个列表,可以按照以下方式进行。当然还有很多其他方法可供选择。
// コピー元
List<String> listX = new ArrayList<String>();
listX.add("foo");
// 最もシンプルな例
List<String> listA = new ArrayList<String>(listX);
// NPEを避け、コピー元がnullなら空のリストを代入する例
List<String> listB = Optional.ofNullable(listX)
.map(e -> new ArrayList<>(e))
.orElseGet(() -> new ArrayList<>());
ArrayList的构造函数接收ArrayList类型的参数,并生成一个新的ArrayList,因此这个操作是有效的。
如果普通地使用“=”进行赋值操作,会发生什么情况?
如果是原始类型,它将按照直观的方式进行操作。它直接处理字面上的原始值,这样自然就可以进行深拷贝了。
int a = 123;
int b = a;
b = 456; // bに代入を行なっても a == 123 のまま
一方面,参照类型(变量)具有指向对象的”引用”,所以如果直接使用”=”运算符,就会导致共享相同的引用状态。
// コピー元
MyClass obj1 = new MyClass();
obj1.setNum(20);
// ディープコピー(新しいオブジェクトを生成して中身のプリミティブ型をコピーする)
MyClass obj2 = new MyClass(); // 新しいオブジェクトを生成している
obj2.setNum(obj1.getNum()); // プリミティブ型(int)が渡される
// 参照を共有している状態
MyClass obj99 = obj1; // これだと生成されるのは「obj1と同じオブジェクトへの参照」を持った「違う変数」
只有String类型有点特殊
关于String2
一个String对象具有恒定(不变)的值。
字符串字面值(§3.10.5)是对String类实例的引用。
当结果不是常量表达式(§15.28)时,字符串连接运算符+(§15.18.1)隐式地创建一个新的String对象。
根据该内容,我们平时随意使用的字符串字面量(例如 String foo = “foo!”; 中的 “foo!”)并不是原始类型,而是对隐含生成的 String 类型实例的“引用”。虽然它们可以进行类似加法的操作,但实际上是生成了一个新的对象,并返回对该对象的“引用”。
顺便提一下,相同的字符串文字直接量会引用相同的String对象3 4。
String hello = "Hello"
String lo = "lo";
hello == "Hello" // true(同じ文字列、同じ参照)
hello == ("Hel"+"lo") // true(コンパイル時に結局 "Hello"になるので)
hello == ("Hel"+lo) // false(文字列結合によって新しいオブジェクトへの参照になってしまった)
JLS-3.10.5 字符串字面量↩
JLS-12.5 新建类实例的创建↩