Java基础之浅克隆和深克隆

在我们的日常开发中可能会暂时需要保存某个对象的拷贝,如果new一个新对象再把值一个一个set进去也不是不可以,但总感觉很蠢,所以Java为我们提供了clone方法,使用这个方法拷贝也就是我们要提到的浅克隆了。

浅克隆

浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。

浅克隆要求克隆的类必须实现Cloneable接口,Cloneable接口与Serializable接口一样也是标记接口,只有实现了这个接口的类才能调用clone方法。clone方法是Object类的本地protected方法,因此还需重写这个方法,把修饰符改为public然后调用Object的clone方法就好了。

例如要克隆的Student类:

class Student implements Serializable,Cloneable{

	private static final long serialVersionUID = 1L;

	private String name;
	private Integer age;
	private String gender;

	public Student(String name, Integer age, String gender, Score score) {
		this.name = name;
		this.age = age;
		this.gender = gender;
		this.score = score;
	}

	public Score getScore() {
		return score;
	}

	public void setScore(Score score) {
		this.score = score;
	}

	private Score score;

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student{" +
				"name='" + name + '\'' +
				", age=" + age +
				", gender='" + gender + '\'' +
				", score=" + score +
				'}';
	}

	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

class Score {

	private Integer mathematics;
	private Integer Chinese;

	public Score(Integer mathematics, Integer chinese) {
		this.mathematics = mathematics;
		Chinese = chinese;
	}

	public Integer getMathematics() {
		return mathematics;
	}

	public void setMathematics(Integer mathematics) {
		this.mathematics = mathematics;
	}

	public Integer getChinese() {
		return Chinese;
	}

	public void setChinese(Integer chinese) {
		Chinese = chinese;
	}

	@Override
	public String toString() {
		return "Score{" +
				"mathematics=" + mathematics +
				", Chinese=" + Chinese +
				'}';
	}
}

测试程序及输出结果:

//测试程序
public static void main(String[] args) {
    Score score = new Score(93,96);
    Student student = new Student("赵彦祖",18,"男",score);
    System.out.println("原始对象:"+student.toString());
    try {
        Student studentCopy = (Student) student.clone();
        System.out.println("拷贝对象:"+studentCopy.toString());
        System.out.println("比较引用类型地址是否相同:"+(student.getScore()==studentCopy.getScore()));
        System.out.println("********修改拷贝对象********");
        studentCopy.setName("charles");
        studentCopy.setAge(20);
        studentCopy.setGender("unknow");
        Score anotherScore = studentCopy.getScore();
        anotherScore.setChinese(59);
        anotherScore.setMathematics(37);
        studentCopy.setScore(anotherScore);
        System.out.println("原始对象:"+student.toString());
        System.out.println("拷贝对象:"+studentCopy.toString());
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}

//输出结果
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
拷贝对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
比较引用类型地址是否相同:true
********修改拷贝对象********
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=37, Chinese=59}}
拷贝对象:Student{name='charles', age=20, gender='unknow', score=Score{mathematics=37, Chinese=59}}

可以看出来浅克隆只是克隆了基本类型的值,String类型的值和引用指向的地址(即原始对象和拷贝对象的Score引用指向统同一地址),因此修改拷贝后的对象的Score对象同样会影响到原始对象。无论如何这都不是我们想要的,这时候我们就需要使用深克隆。

深克隆

深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。

拿上面的例子来说,使用深克隆之后拷贝对象的Score引用指向了一个新的地址,这个新的地址存放的是原始对象的Score对象的拷贝。深克隆是通过序列化和反序列化实现的,序列化和反序列化可以看这篇文章。此处需要注意被序列化的类要实现Serializable接口,类中属性如果有引用类型,则该类同样要实现Serializable。例如在此例中Student类和Score类都需要实现Serializable接口,有任何一个没有实现都会抛出NotSerializableException异常。如果Score类中也有引用类型的属性,则该引用类型同样要实现Serializable接口。

使用深克隆克隆Student类:

//测试程序
public static void main(String[] args) {
    Score score = new Score(93,96);
    Student student = new Student("赵彦祖",18,"男",score);
    System.out.println("原始对象:"+student.toString());
    Student studentCopy = clone(student);
    System.out.println("拷贝对象:"+studentCopy.toString());
    System.out.println("比较引用类型地址是否相同:"+(student.getScore()==studentCopy.getScore()));
    System.out.println("********修改拷贝对象********");
    studentCopy.setName("charles");
    studentCopy.setAge(20);
    studentCopy.setGender("unknow");
    Score anotherScore = studentCopy.getScore();
    anotherScore.setChinese(59);
    anotherScore.setMathematics(37);
    System.out.println("原始对象:"+student.toString());
    System.out.println("拷贝对象:"+studentCopy.toString());
}

//深克隆方法
public static <T>T clone(T object) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = null;
    ByteArrayInputStream bis = null;
    ObjectInputStream ois = null;
    T t = null;
    try {
        oos = new ObjectOutputStream(bos);
        oos.writeObject(object);
        bis = new ByteArrayInputStream(bos.toByteArray());
        ois = new ObjectInputStream(bis);
        t = (T) ois.readObject();
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }finally {
        try {
            ois.close();
            bis.close();
            oos.close();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
    return t;
}

//输出结果
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
拷贝对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
比较引用类型地址是否相同:false
********修改拷贝对象********
原始对象:Student{name='赵彦祖', age=18, gender='男', score=Score{mathematics=93, Chinese=96}}
拷贝对象:Student{name='charles', age=20, gender='unknow', score=Score{mathematics=37, Chinese=59}}

可以看到深克隆后原始对象的Score引用与拷贝对象的Score引用指向的不是同一地址了,当然修改拷贝对象的Score属性也不会影响到原始对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值