最近遇到一个问题就是深拷贝和浅拷贝的区别,通过自己的学习,将自己的掌握的总结一下分享给大家。
深拷贝和浅拷贝其实就是对象的复制,对象的拷贝主要有一下三种形式:
1、传递引用
2、通过clone()方法浅拷贝
3、深拷贝
什么是引用传递,其实就是对象之间的赋值,将一个对象的引用赋值给另外一个对象,这样两个对象指向了同一个堆空间内的对象,通过引用对象随意改变对象中的值,那么另外一个引用也会受到影响,用一张图来说明(自己画的有点丑)
从示意图中可以看出来,如果obj1修改真正对象中的值,那么显然下次调用obj2时肯定也是修改以后的值。这就是引用传递
浅拷贝:真正的将对象拷贝,下面示意图
我们通过obj1.clone()方法实现对象的拷贝,这样的话修改值不会相互影响
但是有一个问题,在真正对象中如果有引用类型,那么使用的是引用。通过实体图说明:
这时候我们修改基本数据类型,如图中的1是不会影响的,但是如果对象中引用类型,那么修改以后还是会互相影响,所以就有下面的概念。
深拷贝:就是拷贝对象,而且拷贝对象中的引用
如图:
所以有了示意图,代码实现就不简单多了,下面贴上代码:
package com.yxc.clone;
/**
* 英雄的介绍类
*/
public class IntroHero {
private String word;
public IntroHero(String word){
this.word=word;
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
}
package com.yxc.clone;
//如果该类要被克隆就必须实现Cloneable接口
public class Hero implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
private int id;
private String name;
private IntroHero intro;
public IntroHero getIntro() {
return intro;
}
public void setIntro(IntroHero intro) {
this.intro = intro;
}
public Hero(int id, String name){
this.id=id;
this.name=name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.yxc.clone;
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
test1(); //测试引用传递
test2(); //测试浅拷贝
test3(); //测试深拷贝
}
/**第一种简单的引用传递*/
public static void test1(){
//引用的复制,hero和hero1引用都指向同一个对象
Hero hero=new Hero(1,"鲁班");
Hero hero1=hero;
hero.setIntro(new IntroHero("是一个射手"));
System.out.println(hero==hero1); //返回true
System.out.println(hero.getIntro()==hero1.getIntro()); //返回true
//修改一个对象的值,观察另一个会改变嘛
hero.setId(2);
System.out.println(hero.getId());
System.out.println(hero1.getId());
/**
* 通过这种方法拷贝的对象其实就是将引用传递过去,两者都指向同一个真正的对象
* 所以不管通过哪一个对象修改值,都会影响另一个对象的内容
*/
}
/**浅拷贝*/
public static void test2(){
Hero hero=new Hero(1,"鲁班");
IntroHero introHero=new IntroHero("是一个射手");
hero.setIntro(introHero);
try {
Hero hero2 = (Hero)hero.clone();
System.out.println(hero==hero2); //返回false
hero.setId(2);
System.out.println(hero.getId()); //2
System.out.println(hero2.getId()); //1
System.out.println(hero.getIntro()==hero2.getIntro()); //true
//下面我们对引用类型的值进行修改
introHero.setWord("攻速强");
hero.setIntro(introHero);
//我们发现下面两者输出的都是攻速强
//这是因为在浅复制中,如果是基本数据类型的,直接将值拷贝过去,如果是引用类型,那么拷贝的对象的引用,
//也就是说如果改变一个对象的值,那么就会影响到另一个对象的值
System.out.println(hero.getIntro().getWord());
System.out.println(hero2.getIntro().getWord());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
/**深拷贝
* 有了浅拷贝的基础,那么我们只要让对象中的引用类型不一样就可以
* */
public static void test3(){
Hero hero=new Hero(1,"鲁班");
IntroHero introHero=new IntroHero("是一个射手");
hero.setIntro(introHero);
try {
Hero hero3 = (Hero)hero.clone();
System.out.println(hero==hero3); //返回false
hero.setId(2);
System.out.println(hero.getId()); //2
System.out.println(hero3.getId()); //1
System.out.println(hero.getIntro()==hero3.getIntro()); //true
//这里是不一样的地方,我们制定对象不一样,这样不管修改哪一个对象,都不影响到另一个对象
hero.setIntro(new IntroHero("攻速强"));
System.out.println(hero.getIntro().getWord());
System.out.println(hero3.getIntro().getWord());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
注意要实现后面两种一定要实现接口然后重写接口中的clone方法
还有一种是真正实现深度拷贝的方法,那就是实现Serializable接口,通过对象的序列化和反序列化来实现,我们直接创建对象,然后修改值,不管是基本数据类型还是引用数据类型,都不会互相影响。
这里贴上一个主要的类,关于序列化和反序列化的
package com.yxc.clone;
import java.io.*;
public class MyUtil {
private MyUtil() {
}
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
}
}