Java基礎(十三)--深拷貝和淺拷貝


在上篇文章:Java基礎(十二)--clone()方法,我們簡單介紹了clone()的使用

clone()對於基本數據類型的拷貝是完全沒問題的,但是如果是引用數據類型呢?

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student implements Cloneable{

    private int id;
    private String name;
    private int sex;
    private Score score;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
} 
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Score {

    private int math;
    private int chinese;
}
public static void main(String[] args) throws Exception{
	Student student = new Student(1001, "sam", 1, new Score(78, 91));
	Student student1 = (Student)student.clone();
	System.out.println(student == student1);

	student1.getScore().setChinese(99);
	System.out.println(student.toString());
}

結果:

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=99))

從結果上看,clone默認實現的是淺拷貝,並沒有達到我們的預期。那么什么是深拷貝和淺拷貝?

深拷貝:在淺拷貝的基礎上,引用變量也進行了clone,並指向clone產生的新對象

淺拷貝:被復制對象的所有值屬性都含有與原來對象的相同,但是對象引用屬性仍然指向原來的對象

clone()如何實現深拷貝?

1、引用成員變量Score需要實現Cloneable接口,並且重寫Object的Clone()

2、自定義Student的Clone()

@Override
protected Object clone() throws CloneNotSupportedException {
	Student student = (Student) super.clone();
	Score score = student.getScore();
	student.setScore((Score)score.clone());
	return student;
}

結果:

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=91))

結論:

  想要通過Clone()實現深拷貝,該對象必須要實現Cloneable接口,實現並且自定義clone(),把引用變量進行clone,然后引用變量對應的類也要實現Cloneable接口並且實現clone方法。

  假如這個對象有很多個引用變量,都要實現clone接口,並且重寫clone(),而且該對象自定義clone(),真的不是太方便,我們還可以序列化來實現深拷貝。

序列化實現深拷貝

可以寫一個序列化實現深拷貝的工具類,兼容所有對象。

public class DeepCloneUtils {

    public static <T extends Serializable> T deepClone(T object) {
        T cloneObject = null;
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.close();

            ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            cloneObject = (T)objectInputStream.readObject();
            objectInputStream.close();

        }  catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return cloneObject;
    }
}

當前類和引用對象對應的類都要實現序列化

public static void main(String[] args) throws Exception{
	Student student = new Student(1001, "sam", 1, new Score(78, 91));
	Student student1 = (Student)DeepCloneUtils.deepClone(student);
	System.out.println(student == student1);

	student1.getScore().setChinese(99);
	System.out.println(student.toString());
}

結果:

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=91))

PS:

  把當前對象寫入到一個字節流中,再從字節流中將其讀出來,這樣就可以創建一個新的對象了,並且該新對象與源對象引用之間沒有關聯。

  序列化實現方式,省略了clone()內部自定義的過程,但是還是要實現序列化的(當前類及引用類)。

  現在有一個問題,如果這個引用對象是第三方jar包呢,我們如果讓它實現Serializable和Cloneable接口,上述兩種解決方案沒法使用了,我們需要新的解決方案。

以下通過第三方jar包實現對象拷貝,不需要實現Serializable和Cloneable接口:

modelMapper、Spring中的BeanUtils、Commons-BeanUtils、cglib、orika等,那么哪些才是深拷貝?

modelMapper實現對象拷貝:

1、首先引用maven依賴

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>1.1.0</version>
</dependency>
public static void main(String[] args) throws Exception{
	Student student = new Student(1001, "sam", 1, new Score(78, 91));
	ModelMapper modelMapper = new ModelMapper();
	Student student1 = new Student();
	modelMapper.map(student, student1);
	System.out.println(student == student1);

	student1.getScore().setChinese(99);
	System.out.println(student.toString());
}

結果:證明ModelMapper實現的是淺拷貝。

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=99))

Spring中的BeanUtils實現對象拷貝:

public static void main(String[] args) throws Exception{
	Student student = new Student(1001, "sam", 1, new Score(78, 91));
	Student student1 = new Student();
	BeanUtils.copyProperties(student, student1);
	System.out.println(student == student1);

	student1.getScore().setChinese(99);
	System.out.println(student.toString());
}

結果:Spring-BeanUtils實現的是淺拷貝。

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=99))

Commons-BeanUtils實現對象拷貝:

1、首先引用maven依賴

<dependency>
  <groupId>commons-beanutils</groupId>
  <artifactId>commons-beanutils</artifactId>
  <version>1.8.0</version>
</dependency>
public static void main(String[] args) throws Exception{
	Student student = new Student(1001, "sam", 1, new Score(78, 91));
	Student student1 = new Student();
	BeanUtils.copyProperties(student1, student);
	System.out.println(student == student1);

	student1.getScore().setChinese(99);
	System.out.println(student.toString());
}

結果:證明ModelMapper實現的是淺拷貝。

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=99))

Cglib實現對象拷貝:

1、首先引用maven依賴

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib-nodep</artifactId>
  <version>3.1</version>
</dependency>
public static void main(String[] args) throws Exception{
	Student student = new Student(1001, "sam", 1, new Score(78, 91));
	Student student1 = new Student();
	BeanCopier beanCopier = BeanCopier.create(Student.class, Student.class, false);
	beanCopier.copy(student, student1, null);
	System.out.println(student == student1);

	student1.getScore().setChinese(99);
	System.out.println(student.toString());
}

結果:Cglib實現的依然是淺拷貝,感覺很扎心啊。。。

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=99))

PS:網上有說cglib實現自定義轉換器可以實現深拷貝,但是我試驗下來還是不能,各位可以試驗一下,如果可以,請留言。。。

orika實現對象拷貝:

<dependency>
  <groupId>ma.glasnost.orika</groupId>
  <artifactId>orika-core</artifactId>
  <version>1.5.0</version>
</dependency>
</dependencies>
public static void main(String[] args) throws Exception{
	MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
	mapperFactory.classMap(Student.class, Student.class)
			.byDefault()
			.register();
	ConverterFactory converterFactory = mapperFactory.getConverterFactory();
	MapperFacade mapper = mapperFactory.getMapperFacade();

	Student student = new Student(1001, "sam", 1, new Score(78, 91));
	Student student1 = mapper.map(student, Student.class);
	System.out.println(student == student1);

	student1.getScore().setChinese(99);
	System.out.println(student.toString());
}

結果:可以實現深拷貝

false
Student(id=1001, name=sam, sex=1, score=Score(math=78, chinese=91))

參考:

https://blog.csdn.net/54powerman/article/details/64920431?locationNum=6&fps=1

https://blog.csdn.net/weixin_40581980/article/details/81388557


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM