title: java深浅拷贝
date: 2018-12-16 15:54:20
updated: 2020-03-15 13:29:14
categories: java
tags:
- java
此文档为java深浅拷贝的学习总结
Java 浅拷贝和深拷贝
来源:Java 浅拷贝和深拷贝
1.直接赋值
A1 a1 = a2;
那么对象a1/a2指向同一个对象,即a2对对象的操作,a1也跟着变了
2.浅拷贝
类的方法中加个clone方法。这种浅拷贝实现的是:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。
private String name; //姓名
private String sex; //性别
private int age; //年龄
private String experience; //工作经历
public Object clone() {
try {
return (Resume)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
Resume zhangsan = new Resume("zhangsan","男",24);
zhangsan.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码拷贝和粘贴");
zhangsan.displayResume();
Resume zhangsan1 = (Resume)zhangsan.clone();
zhangsan1.setAge(23);
zhangsan1.displayResume();
Resume zhangsan2 = (Resume)zhangsan.clone();
zhangsan2.setExperience("2009-2013就读于家里蹲大学,精通JAVA,C,C++,C#等代码");
zhangsan2.displayResume();
zhangsan.displayResume();
}
上面代码没问题,因为数据没有引用类型,a1/a2指向对象不同,彼此对立,但是下面的就不行了:
class Experience {
private String educationBackground;
private String skills;
public void setExperience(String educationBackground, String skills) {
// TODO Auto-generated constructor stub
this.educationBackground = educationBackground;
this.skills = skills;
}
public String toString() {
return educationBackground + skills;
}
}
/* 建立类,实现Clone方法 */
class Resume implements Cloneable{
private String name; //姓名
private String sex; //性别
private int age; //年龄
private Experience experience; //工作经历,引用类型
public Resume(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
this.experience = new Experience();
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public Experience getExperience() {
return experience;
}
public void setExperience(String educationBackground, String skills) {
experience.setExperience(educationBackground, skills);
}
public void displayResume() {
System.out.println("姓名:"+name+" 性别:"+sex+" 年龄:"+age);
System.out.println("工作经历:"+experience.toString());
}
public Object clone() {
try {
return (Resume)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public class MainClass {
public static void main(String[] args) {
Resume zhangsan = new Resume("zhangsan","男",24);
zhangsan.setExperience("2009-2013就读于家里蹲大学","精通JAVA,C,C++,C#等代码拷贝和粘贴");
zhangsan.displayResume();
Resume zhangsan2 = (Resume)zhangsan.clone();
zhangsan2.setExperience("2009-2013就读于家里蹲大学","精通JAVA,C,C++,C#等");
zhangsan2.displayResume();
zhangsan.displayResume();
zhangsan2.displayResume();
}
}
out:
姓名:zhangsan 性别:男 年龄:24
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等代码拷贝和粘贴
姓名:zhangsan 性别:男 年龄:24
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等
姓名:zhangsan 性别:男 年龄:24
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等
姓名:zhangsan 性别:男 年龄:24
工作经历:2009-2013就读于家里蹲大学精通JAVA,C,C++,C#等
如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。”其实也就是说,zhangsan和zhangsan2里面的Experience类指向的是同一个对象,不管是zhangsan里面的Experience变化,还是zhangsan2里面的Experience变化都会影响另外一个。
3.深拷贝
java深拷贝两种方法:
- 1.继承Cloneable重写clone方法
- 2.对象实现Serializable通过序列化实现深拷贝
其实出现问题的关键就在于clone()方法上,我们知道该clone()方法是使用Object类的clone()方法,但是该方法存在一个缺陷,它并不会将对象的所有属性全部拷贝过来,而是有选择性的拷贝,基本规则如下:
1、 基本类型
如果变量是基本很类型,则拷贝其值,比如int、float等。
2、 String字符串
若变量为String字符串,则拷贝其地址引用。但是在修改时,它会从字符串池中重新生成一个新的字符串,原有对象保持不变。
3、 对象/引用类型
如果变量是一个实例对象,则拷贝其地址引用,也就是说此时新对象与原来对象是公用该实例变量。(即浅拷贝)
因此深拷贝就是在clone方法中把引用类型深拷贝即可。
public class YuelyLog implements Cloneable {
private Attachment attachment;//引用类型
private String name;
private String date;
@Override
protected YuelyLog clone() throws CloneNotSupportedException {
YuelyLog o = (YuelyLog)super.clone(); //Object.clone()
o.attachment = (Attachment)attachment.clone();
return o;
}
}
三点说明:
-
1、clone方法实现要点:实现 Cloneable接口 + 使用protected访问修饰符重新定义clone方法以实现深拷贝
-
2、clone方法是Object类的一个protected方法,因此无法调用anObj.clone(),也就是说用户在编写的代码中不能直接调用它,只有Employee类才能克隆Employee对象(这样通过类来拷贝将避免出现浅拷贝的 问题)
-
3、Cloneable接口是java提供的几个标记接口(tagging interface),与通常接口不同的是它没有指定方法,Cloneable中的clone方法是从Object类继承而来,这个Cloneable接口作为标记是想说明如果一个对象需要克隆而没有实现Cloneable接口,就会产生一个异常。
深拷贝问题解决了,但对于上面的解决方案还是存在一个问题,若我们系统中存在大量的对象是通过拷贝生成的,如果我们每一个类都写一个clone()方法,并将还需要进行深拷贝,新建大量的对象,这个工程是非常大的,这里我们可以利用对象的序列化来实现对象的拷贝。
如何利用序列化来完成对象的拷贝呢?在内存中通过字节流的拷贝是比较容易实现的。把母对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与母对象之间并不存在引用共享的问题,真正实现对象的深拷贝。
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
使用该工具类的对象必须要实现Serializable接口,否则是没有办法实现克隆的。
public class Person implements Serializable{
private static final long serialVersionUID = 2631590509760908280L;
..................
//去除clone()方法
}
public class Email implements Serializable{
private static final long serialVersionUID = 1267293988171991494L;
....................
}
使用该工具类的对象只要实现Serializable接口就可实现对象的克隆,无须实现Cloneable接口中的clone()方法。
public class Client {
public static void main(String[] args) {
//写封邮件
Email email = new Email("请参加会议","请与今天12:30到二会议室参加会议...");
Person person1 = new Person("张三",email);
Person person2 = CloneUtils.clone(person1);
person2.setName("李四");
Person person3 = CloneUtils.clone(person1);
person3.setName("王五");
person1.getEmail().setContent("请与今天12:00到二会议室参加会议...");
System.out.println(person1.getName() + "的邮件内容是:" + person1.getEmail().getContent());
System.out.println(person2.getName() + "的邮件内容是:" + person2.getEmail().getContent());
System.out.println(person3.getName() + "的邮件内容是:" + person3.getEmail().getContent());
}
}
-------------------
Output:
张三的邮件内容是:请与今天12:00到二会议室参加会议...
李四的邮件内容是:请与今天12:30到二会议室参加会议...
王五的邮件内容是:请与今天12:30到二会议室参加会议...