1.首先,你要知道怎么实现克隆:实现Cloneable接口,在bean里面重写clone()方法,权限为public。
2.其次,你要大概知道什么是地址传递,什么是值传递。
3.最后,你要知道你为什么使用这个clone方法。
先看第一条,简单的克隆代码的实现。这个也就是我们在没了解清楚这个Java的clone的时候,会出现的问题。
看完代码,我再说明这个时候的问题。
先看我要克隆的学生bean的代码:
- package com.lxk.model;
- /**
- * 学生类:有2个属性:1,基本属性-String-name;2,引用类型-Car-car。
- * <p>
- * Created by lxk on 2017/3/23
- */
- public class Student implements Cloneable {
- private String name;
- private Car car;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Car getCar() {
- return car;
- }
- public void setCar(Car car) {
- this.car = car;
- }
- @Override
- public String toString() {
- return "Student{" +
- "name='" + name + '\'' +
- ", car=" + car +
- '}';
- }
- @Override
- public Student clone() {
- Student student = null;
- try {
- student = (Student) super.clone();
- } catch (CloneNotSupportedException ignored) {
- System.out.println(ignored.getMessage());
- }
- return student;
- }
- }
学生内部引用了Car这个bean
- package com.lxk.model;
- import java.util.List;
- public class Car implements Comparable<Car> {
- private String sign;
- private int price;
- private List<Dog> myDog;
- private List<String> boys;
- public Car() {
- }
- public Car(String sign, int price) {
- this.sign = sign;
- this.price = price;
- }
- public Car(String sign, int price, List<Dog> myDog) {
- this.sign = sign;
- this.price = price;
- this.myDog = myDog;
- }
- public Car(String sign, int price, List<Dog> myDog, List<String> boys) {
- this.sign = sign;
- this.price = price;
- this.myDog = myDog;
- this.boys = boys;
- }
- public String getSign() {
- return sign;
- }
- public void setSign(String sign) {
- this.sign = sign;
- }
- public int getPrice() {
- return price;
- }
- public void setPrice(int price) {
- this.price = price;
- }
- public List<Dog> getMyDog() {
- return myDog;
- }
- public void setMyDog(List<Dog> myDog) {
- this.myDog = myDog;
- }
- public List<String> getBoys() {
- return boys;
- }
- public void setBoys(List<String> boys) {
- this.boys = boys;
- }
- @Override
- public int compareTo(Car o) {
- //同理也可以根据sign属性排序,就不举例啦。
- return this.getPrice() - o.getPrice();
- }
- @Override
- public String toString() {
- return "Car{" +
- "sign='" + sign + '\'' +
- ", price=" + price +
- ", myDog=" + myDog +
- ", boys=" + boys +
- '}';
- }
- }
最后就是main测试类
- package com.lxk.findBugs;
- import com.lxk.model.Car;
- import com.lxk.model.Student;
- /**
- * 引用传递也就是地址传递需要注意的地方,引起的bug
- * <p>
- * Created by lxk on 2017/3/23
- */
- public class Bug2 {
- public static void main(String[] args) {
- Student student1 = new Student();
- Car car = new Car("oooo", 100);
- student1.setCar(car);
- student1.setName("lxk");
- //克隆完之后,student1和student2应该没关系的,修改student1不影响student2的值,但是完之后发现,你修改car的值,student2也受影响啦。
- Student student2 = student1.clone();
- System.out.println("学生2:" + student2);//先输出student2刚刚克隆完之后的值,然后在修改student1的相关引用类型的属性值(car)和基本属性值(name)
- car.setSign("X5");
- student1.setName("xxx");
- System.out.println("学生2:" + student2);//再次输出看修改的结果
- }
- }
之后就该是执行的结果图了:

对上面执行结果的疑惑,以及解释说明:
我们可能觉得自己在bean里面实现clone接口,重写了这个clone方法,那么学生2是经由学生1clone,复制出来的,
那么学生1和学生2,应该是毫不相干的,各自是各自,然后,在修改学生1的时候,学生2是不会受影响的。
但是结果,不尽人意。从上图执行结果可以看出来,除了名字,这个属性是没有被学生1影响,关于car的sign属性已经因为学生1的变化而变化,这不是我希望的结果。
可见,这个简单的克隆实现也仅仅是个“浅克隆”,也就是基本类型数据,他是会给你重新复制一份新的,但是引用类型的,他就不会重新复制份新的。引用类型包括,上面的其他bean的引用,list集合,等一些引用类型。
那么怎么实现深克隆呢?
对上述代码稍作修改,如下:
学生bean的clone重写方法如下所示:
- @Override
- public Student clone() {
- Student student = null;
- try {
- student = (Student) super.clone();
- if (car != null) {
- student.setCar(car.clone());
- }
- } catch (CloneNotSupportedException ignored) {
- System.out.println(ignored.getMessage());
- }
- return student;
- }
然后还要Car类实现cloneable接口,复写clone方法:
- @Override
- public Car clone() {
- Car car = null;
- try {
- car = (Car) super.clone();
- if (myDog != null) {
- car.setMyDog(Lists.newArrayList(myDog));
- }
- if (boys != null) {
- car.setBoys(Lists.newArrayList(boys));
- }
- } catch (CloneNotSupportedException ignored) {
- System.out.println(ignored.getMessage());
- }
- return car;
- }
主测试代码不动,这个时候的执行结果如下:

可以看到,这个时候,你再修改学生1的值,就不会影响到学生2的值,这才是真正的克隆,也就是所谓的深克隆。
怎么举一反三?
可以看到,这个例子里面的引用类型就一个Car类型的属性,但是实际开发中,除了这个引用其他bean类型的属性外,可能还要list类型的属性值用的最多。
那么要怎么深克隆呢,就像我在Car bean类里面做的那样,把所有的引用类型的属性,都在clone一遍。那么你在最上层调用这个clone方法的时候,他就是真的深克隆啦。
我代码里面那么判断是为了避免空指针异常。当然,这个你也得注意咯。
注意 重写clone方法的时候,里面各个属性的null的判断哦。
上面的是override clone()
方法来实现深克隆的。如果你这个要克隆的对象很复杂的话,你就不得不去每个引用到的对象去复写这个clone方法,这个太啰嗦来,改的地方,太多啦。
还有个方法就是使用序列化来实现这个深拷贝
- /**
- * 对象的深度克隆,此处的对象涉及Collection接口和Map接口下对象的深度克隆
- * 利用序列化和反序列化的方式进行深度克隆对象
- *
- * @param object 待克隆的对象
- * @param <T> 待克隆对象的数据类型
- * @return 已经深度克隆过的对象
- */
- public static <T extends Serializable> T deepCloneObject(T object) {
- T deepClone = null;
- ByteArrayOutputStream baos = null;
- ObjectOutputStream oos = null;
- ByteArrayInputStream bais = null;
- ObjectInputStream ois = null;
- try {
- baos = new ByteArrayOutputStream();
- oos = new ObjectOutputStream(baos);
- oos.writeObject(object);
- bais = new ByteArrayInputStream(baos
- .toByteArray());
- ois = new ObjectInputStream(bais);
- deepClone = (T)ois.readObject();
- } catch (IOException | ClassNotFoundException e) {
- e.printStackTrace();
- } finally {
- try {
- if(baos != null) {
- baos.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- try {
- if(oos != null) {
- oos.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- try{
- if(bais != null) {
- bais.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- try{
- if(ois != null) {
- ois.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return deepClone;
- }
具体的使用如下:
- /**
- * 使用序列化来实现深拷贝简单。但是,所涉及到的所有对象都的实现序列化接口。
- */
- private static void cloneBySerializable() {
- Student student1 = new Student();
- Car car = new Car("oooo", 100, Lists.newArrayList(new Dog("aaa", true, true)));
- student1.setCar(car);
- student1.setName("lxk");
- Student student2 = deepCloneObject(student1);
- System.out.println("学生2:" + student2);
- car.setSign("X5");
- car.setMyDog(null);
- student1.setName("xxx");
- System.out.println("学生2:" + student2);
- }
实现的效果,还是和上面的一样的,但是这个就简单多来,只需要给涉及到的每个引用类型,都去实现序列化接口就好啦。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对w3xue的支持。如果你想了解更多相关内容请查看下面相关链接