1. 寫在前面
今天遇到了這樣一個問題,事實上這個問題是之前遇到過的。
java 中列表的賦值的問題。
這個問題核心是 deep copy
& shallow copy
的問題
2. 情景再現
public class MikeTest {
public static void main(String[] args) throws NoSuchMethodException {
class Person{
private String name;
private Integer age;
Person(String name, Integer age){
this.name = name;
this.age = age;
}
}
// TODO: 2022/3/8 測試一下這個 bug
List<Person> rawList = new ArrayList<>();
Person person1 = new Person("mike",24);
Person person2 = new Person("John",28);
rawList.add(person1);
rawList.add(person2);
List<Person> updatedList = rawList;
for (Person person : updatedList){
person.age = 90;
}
System.out.println(updatedList);
System.out.println(rawList);
}
}
上述代碼運行之后,問題如下:
bug結果
3. 問題原因
List<Person> updatedList = rawList;
這一句代碼,事實上是將指針給過來了,因此兩個 List 對應的是同樣的內容
update 之后,新的變了,原來的也變了(因為是一個)
4. 怎么解
一開始我以為下面的方式就可以解決(因為大一學c++課的時候,記得有這么講過)
List<Person> updatedList = new ArrayList(rawList);
但是最后搞完了,發現問題還是沒有解決
5. deep copy
后續去查了一下,這里其實是 shallow copy
& deep copy
的問題。
shallow copy
這就是在上述 3
部分中所描述的,shallow copy
只會將地址指到相同的地方,不會重新創建對象。
deep copy
deep copy
就是要一份新的引用對象。
實現則需要元素類去實現 Cloneable
接口, @Override
其中的 clone()
方法。
看代碼
/**
* 實現 Cloneable 的類 對象是人
* 完成深拷貝
* 事實上,為了完成深拷貝,是需要被拷貝的元素具有這樣的能力
*
* @author mikeshine
*/
@Data
public class Person implements Cloneable{
/**
* 名字
*/
private String name;
/**
* 年齡
*/
private Integer age;
Person(String name, Integer age){
this.age = age;
this.name = name;
}
/**
* clone 方法的核心目的就是 返回一個開辟新地址空間的 對象
*
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Person clone() throws CloneNotSupportedException {
// 首先給新對象開辟一個地址空間
Person newPerson = (Person)super.clone();
// 然后依次將屬性 copy
newPerson.setName(this.name);
newPerson.setAge(this.age);
return newPerson;
}
}
使用如下
List<Person> rawList = new ArrayList<>();
Person person1 = new Person("mike",24);
Person person2 = new Person("John",28);
rawList.add(person1);
rawList.add(person2);
List<Person> updatedList = new ArrayList<>();
for(Person person : rawList){
// 這里是重點,相當于用 clone() 方法重新生成一個新對象放在新地址
updatedList.add(person.clone());
}