Java中的浅拷贝与深拷贝
Object中的clone方法:
protected native Object clone() throws CloneNotSupportedException;
创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,表达式: x.clone() != x 为 true,表达式: x.clone().getClass() == x.getClass() 也为 true,但这些并非必须要满足的要求。一般情况下: x.clone().equals(x) 为 true,但这并非必须要满足的要求。 按照惯例,返回的对象应该通过调用 super.clone 获得。如果一个类及其所有的超类(Object 除外)都遵守此约定,则 x.clone().getClass() == x.getClass()。
按照惯例,此方法返回的对象应该独立于该对象(正被复制的对象)。要获得此独立性,在 super.clone 返回对象之前,有必要对该对象的一个或多个字段进行修改。这通常意味着要复制包含正在被复制对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改 super.clone 返回的对象中的字段。
Object 类的 clone 方法执行特定的复制操作。首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。注意,所有的数组都被视为实现接口 Cloneable。否则,此方法会创建此对象的类的一个新实例,并像通过分配那样,严格使用此对象相应字段的内容初始化该对象的所有字段;这些字段的内容没有被自我复制。所以,此方法执行的是该对象的“浅表复制”,而不“深层复制”操作。
Object 类本身不实现接口 Cloneable,所以在类为 Object 的对象上调用 clone 方法将会导致在运行时抛出异常。
在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。继承自java.lang.Object类的clone()方法是浅复制。
②在派生类中覆盖基类的clone()方法,并声明为public。
③在派生类的clone()方法中,调用super.clone()。
④在派生类中实现Cloneable接口(一个标识性的接口)。
深复制实例
创建Employer类,实现Cloneable接口:
class Employer implements Cloneable{ private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
创建Employee类,实现Cloneable接口,并改写clone方法,实现深复制:
class Employee implements Cloneable{ private String username; private Employer employer; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Employer getEmployer() { return employer; } public void setEmployer(Employer employer) { this.employer = employer; } @Override public Object clone() throws CloneNotSupportedException { //克隆Employee对象并手动的进一步克隆Employee对象中包含的Employer对象 Employee employee = (Employee)super.clone(); employee.setEmployer((Employer) employee.getEmployer().clone()); return employee; } }
这样,在客户端拷贝的两个Employee对象的Employer就互不影响了:
public static void main(String[] args) throws CloneNotSupportedException { Employer employer = new Employer(); employer.setUsername("arthinking"); Employee employee = new Employee(); employee.setUsername("Jason"); employee.setEmployer(employer); //employee2由employee深复制得到 Employee employee2 = (Employee) employee.clone(); //这样两个employee各自保存了两个employer employee2.getEmployer().setUsername("Jason"); System.out.println(employee.getEmployer().getUsername()); System.out.println(employee2.getEmployer().getUsername()); }
使用序列化来进行对象的深复制:
序列化即是把对象写到流里面的过程;反序列化即是把对象从流中读取出来的过程。写在流里的是对象的一个拷贝,而原来的对象仍然在JVM里面。
然后使对象实现Serializable接口。把对象写入到一个流里(不用依赖于文件,直接暂存在内存中即可),在从流里读取出来,便得到了一个深复制的对象。
创建Employer2类实现序列化接口:
class Employer2 implements Serializable{ private static final long serialVersionUID = 1L; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
创建Employee2类实现序列化接口,并通过序列化编写深复制的方法:
class Employee2 implements Serializable{ private static final long serialVersionUID = 3969438177161438988L; private String name; private Employer2 employer; public String getName() { return name; } public void setName(String name) { this.name = name; } public Employer2 getEmployer() { return employer; } public void setEmployer(Employer2 employer) { this.employer = employer; } /** * 实现深复制的方法 */ public Object deepCopy() throws IOException, ClassNotFoundException{ //字节数组输出流,暂存到内存中 ByteArrayOutputStream bos = new ByteArrayOutputStream(); //序列化 ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); //反序列化 return ois.readObject(); } }
在main方法中使用序列化深复制对象:
public static void main(String[] args) throws IOException, ClassNotFoundException { Employer2 employer = new Employer2(); employer.setName("arthinking"); Employee2 employee = new Employee2(); employee.setName("Jason"); employee.setEmployer(employer); //通过深复制创建employee2 Employee2 employee2 = (Employee2) employee.deepCopy(); employee2.getEmployer().setName("Jason"); System.out.println(employee.getEmployer().getName()); System.out.println(employee2.getEmployer().getName()); }
serialVersionUID是long类型的。在Eclipse中有两种生成方式:
默认的是1L:
private static final long serialVersionUID = 1L;
另外一个则是根据类名、接口名、成员方法以及属性等生成一个64位的哈希字段:
private static final long serialVersionUID = 3969438177161438988L;
如果没有提供serialVersionUID,对象序列化后存到硬盘上之后,再增加或减少类的filed。这样,当反序列化时,就会出现Exception,造成不兼容问题。
但当serialVersionUID相同时,它就会将不一样的field以type的缺省值反序列化。这样就可以避开不兼容问题了。
Marker Interface:
标识接口,没有定义任何的方法,如Cloneable和Serializable接口。