克隆方法的由来
问题一:什么是克隆(clone)方法
答: 创建并返回此对象的一个副本——按照原对象,创建一个新的对象(复制原对象的内容)。
问题二:已经存在 new 关键字和反射技术都可以创建对象,为什么还需要一个 Object 的 clone 方法呢?
答:必然是 new 关键字和反射技术,存在一些弊端。
new 关键字和反射创建弊端:
通过 new 和反射可以创建内容一模一样的对象。但是,创建对象之后,通过 setter 方法,完成设置(不一样的内容),如果需要创建更多的内容一致的对象,那么 setter 方法调用就不断在重复。
使用 clone 方法创建对象
Object 的 clone 方法使用步骤:
在需要调用 clone 方法的对象上添加实现 Cloneable 接口;
复写 clone 方法,在自己的 clone 方法中调用父类的 clone 方法,将返回值类型强转成本类类型,将当前 clone 方法修饰符改成 public;
调用对象的 clone 方法;
代码演示:
public class Person implements Cloneable{
private String name;
private int age;
//....省略get/set/toString方法
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
复制代码
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person();
p1.setName("张三");
p1.setAge(18);
Person p2 = p1.clone();
System.out.println(p1+":"+p1.hashCode());
System.out.println(p2+":"+p2.hashCode());
}
}
//控制台打印结果:
Person{name='张三', age=18}:1908153060
Person{name='张三', age=18}:116211441
复制代码
通过使用 clone 方法,我们发现大大的减少了创建重复对象代码。这也就是 clone 方法存在的意义。
克隆出来的对象和原来的对象有什么关系:
我们已经知道了,克隆出来的对象内容一致,但是对象哈希值不同,所以是不同对象。
那么两个对象的内容之间有什么关联呢——两个对象的内容是彼此独立,还是,两个对象底层使用的同一个内容呢?
代码演示:
public class Person implements Cloneable{
private String name;
private Integer age;
private Children child;
//....省略get/set/toString方法
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
复制代码
public class Children {
private String name;
private Integer age;
//....省略get/set/toString方法
}
复制代码
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person();
p1.setName("张三");
p1.setAge(28);
Children children1 = new Children();
children1.setName("张伟");
children1.setAge(5);
p1.setChild(children1);
Person p2 = p1.clone();
System.out.println(p1+":对象的哈希值:"+p1.hashCode()+":child成员变量的哈希值:"+p1.getChild().hashCode());
System.out.println(p2+":对象的哈希值:"+p2.hashCode()+":child成员变量的哈希值:"+p2.getChild().hashCode());
}
}
//控制台打印结果
Person{name='张三', age=28, child=Children{name='张伟', age=5}}:
对象的哈希值:116211441:child成员变量的哈希值:607635164
Person{name='张三', age=28, child=Children{name='张伟', age=5}}:
对象的哈希值:529116035:child成员变量的哈希值:607635164
复制代码
结论:通过测试发现克隆出来的对象虽然不一致,但是底层的成员变量的哈希值是一致的。
这种复制我们称之为:浅表复制。
浅表复制的内存结构:
浅表复制的弊端:
由于浅表复制导致克隆的对象中成员变量的底层哈希值一致,如果我们操作其中一个对象的成员变量内容,就会导致,所有的克隆对象的成员内容发送改变。
结论:clone 方法默认的复制操作是浅表复制,浅表复制存在弊端——仅仅创建新的对象,对象的成员内容底层哈希值是一致的,因此,不管是原对象还是克隆对象,只有其中一个修改了成员的数据,就会影响所有的原对象和克隆对象。
要解决浅表复制的问题:进行深层的复制。
深层复制
目的:不仅在执行克隆的时候,克隆对象是一个新对象,而且,克隆对象中的成员变量,也要求是一个新的对象
开发步骤:
修改 children 类实现 Cloneable 接口;
修改 children 类重写 clone 方法;
修改 Person 类重写 clone 方法,在 clone 方法中调用 children 的 clone 方法;
代码演示:
public class Children implements Cloneable {
private String name;
private Integer age;
//....省略get/set/toString方法
@Override
public Children clone() throws CloneNotSupportedException {
return (Children) super.clone();
}
}
复制代码
public class Person implements Cloneable{
private String name;
private Integer age;
private Children child;
//....省略get/set/toString方法
@Override
public Person clone() throws CloneNotSupportedException {
Person clone = (Person) super.clone();
clone.setChild(child.clone());
return clone;
}
}
复制代码
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person();
p1.setName("张三");
p1.setAge(28);
Children children1 = new Children();
children1.setName("张伟");
children1.setAge(5);
p1.setChild(children1);
Person p2 = p1.clone();
System.out.println(p1.getChild());
System.out.println(p2.getChild());
children1.setName("张三丰");
System.out.println(p1.getChild());
System.out.println(p2.getChild());
Children children2 = p2.getChild();
children2.setName("张无忌");
System.out.println(p1.getChild());
System.out.println(p2.getChild());
System.out.println(p1.getChild().hashCode());
System.out.println(p2.getChild().hashCode());
}
}
//控制台打印结果
Children{name='张伟', age=5}
Children{name='张伟', age=5}
Children{name='张三丰', age=5}
Children{name='张伟', age=5}
Children{name='张三丰', age=5}
Children{name='张无忌', age=5}
116211441
607635164
复制代码
可以看到,深层复制成员变量如果是对象,那么也会创建新的对象。
使用 clone 接口实现深层复制的弊端:
虽然深度复制成员变量对象也是创建新的对象,但是修改类中成员变量对应的源码,如果成员变量特别多,那么就需要修改多个类的源码;
比如:前面的 Person 类中现在又新增了 Son、Sister、Mother 啥全家福的,那对应需要修改的类就特别多了;
使用 IO 进行克隆复制(深层复制—克隆涉及的所有的类实现 Serializable 接口)
简单演示:一个对象的复制。
开发步骤:
创建 ByteArrayOutputStream,将数据可以转换成字节;
创建 ObjectOutputStream,关联 ByteArrayOutputStream;
使用 ObjectOutputStream 的 writeObject,读取要复制的对象;
使用 ByteArrayInputStream 读取 ByteArrayOutputStream 的转换的对象字节数据;
创建 ObjectInputStream 读取对象字节数据,创建新的对象;
代码演示:
import java.io.Serializable;
public class User implements Serializable {
private String name;
private int age;
//省略get/set/toString方法
}
复制代码
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
User user = new User();
user.setName("李四");
user.setAge(18);
//1. 创建ByteArrayOutputStream,将数据可以转换成字节
ByteArrayOutputStream bout = new ByteArrayOutputStream();
//2. 创建ObjectOutputStream,关联ByteArrayOutputStream
ObjectOutputStream out = new ObjectOutputStream(bout);
//3. 使用ObjectOutputStream的writeObject,读取要复制的对象
out.writeObject(user);
//4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转换的对象字节数据
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
//5. 创建ObjectInputStream读取对象字节数据,创建新的对象
ObjectInputStream in = new ObjectInputStream(bin);
User obj = (User) in.readObject();
System.out.println(user+":"+user.hashCode());
System.out.println(obj + ":" + obj.hashCode());
}
}
//控制台打印结果
User{name='李四', age=18}:2080166188
User{name='李四', age=18}:1626877848
复制代码
使用 IO 改写 Person 的 clone 方法
开发步骤:
克隆涉及的所有的类实现 Serializable 接口;
修改 Person 类的 clone 方法,使用 IO 复制对象;
@Override
public Person clone() {
try {
//1. 创建ByteArrayOutputStream,将数据可以转换成字节
ByteArrayOutputStream bout = new ByteArrayOutputStream();
//2. 创建ObjectOutputStream,关联ByteArrayOutputStream
ObjectOutputStream out = new ObjectOutputStream(bout);
//3. 使用ObjectOutputStream的writeObject,读取要复制的对象
out.writeObject(this);
//4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转换的对象字节数据
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
//5. 创建ObjectInputStream读取对象字节数据,创建新的对象
ObjectInputStream in = new ObjectInputStream(bin);
Person clone = (Person) in.readObject();
return clone;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
复制代码
public class Test {
public static void main(String[] args) throws Exception {
Person p1 = new Person();
p1.setName("张三");
p1.setAge(58);
Children children1 = new Children();
children1.setName("张伟");
children1.setAge(25);
p1.setChild(children1);
Person p2 = p1.clone();
System.out.println(p1.getChild()+":"+p1.getChild().hashCode());
System.out.println(p2.getChild()+":"+p2.getChild().hashCode());
}
}
//控制台打印
Children{name='张伟', age=25}:1846274136
Children{name='张伟', age=25}:284720968
复制代码
评论