经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
Java Clone深拷贝与浅拷贝的两种实现方法
来源:jb51  时间:2018/10/24 9:34:25  对本文有异议

1.首先,你要知道怎么实现克隆:实现Cloneable接口,在bean里面重写clone()方法,权限为public。
2.其次,你要大概知道什么是地址传递,什么是值传递。
3.最后,你要知道你为什么使用这个clone方法。

先看第一条,简单的克隆代码的实现。这个也就是我们在没了解清楚这个Java的clone的时候,会出现的问题。

看完代码,我再说明这个时候的问题。

先看我要克隆的学生bean的代码:

  1. package com.lxk.model;
  2. /**
  3. * 学生类:有2个属性:1,基本属性-String-name;2,引用类型-Car-car。
  4. * <p>
  5. * Created by lxk on 2017/3/23
  6. */
  7. public class Student implements Cloneable {
  8. private String name;
  9. private Car car;
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. public Car getCar() {
  17. return car;
  18. }
  19. public void setCar(Car car) {
  20. this.car = car;
  21. }
  22. @Override
  23. public String toString() {
  24. return "Student{" +
  25. "name='" + name + '\'' +
  26. ", car=" + car +
  27. '}';
  28. }
  29. @Override
  30. public Student clone() {
  31. Student student = null;
  32. try {
  33. student = (Student) super.clone();
  34. } catch (CloneNotSupportedException ignored) {
  35. System.out.println(ignored.getMessage());
  36. }
  37. return student;
  38. }
  39. }

学生内部引用了Car这个bean

  1. package com.lxk.model;
  2. import java.util.List;
  3. public class Car implements Comparable<Car> {
  4. private String sign;
  5. private int price;
  6. private List<Dog> myDog;
  7. private List<String> boys;
  8. public Car() {
  9. }
  10. public Car(String sign, int price) {
  11. this.sign = sign;
  12. this.price = price;
  13. }
  14. public Car(String sign, int price, List<Dog> myDog) {
  15. this.sign = sign;
  16. this.price = price;
  17. this.myDog = myDog;
  18. }
  19. public Car(String sign, int price, List<Dog> myDog, List<String> boys) {
  20. this.sign = sign;
  21. this.price = price;
  22. this.myDog = myDog;
  23. this.boys = boys;
  24. }
  25. public String getSign() {
  26. return sign;
  27. }
  28. public void setSign(String sign) {
  29. this.sign = sign;
  30. }
  31. public int getPrice() {
  32. return price;
  33. }
  34. public void setPrice(int price) {
  35. this.price = price;
  36. }
  37. public List<Dog> getMyDog() {
  38. return myDog;
  39. }
  40. public void setMyDog(List<Dog> myDog) {
  41. this.myDog = myDog;
  42. }
  43. public List<String> getBoys() {
  44. return boys;
  45. }
  46. public void setBoys(List<String> boys) {
  47. this.boys = boys;
  48. }
  49. @Override
  50. public int compareTo(Car o) {
  51. //同理也可以根据sign属性排序,就不举例啦。
  52. return this.getPrice() - o.getPrice();
  53. }
  54. @Override
  55. public String toString() {
  56. return "Car{" +
  57. "sign='" + sign + '\'' +
  58. ", price=" + price +
  59. ", myDog=" + myDog +
  60. ", boys=" + boys +
  61. '}';
  62. }
  63. }

最后就是main测试类

  1. package com.lxk.findBugs;
  2. import com.lxk.model.Car;
  3. import com.lxk.model.Student;
  4. /**
  5. * 引用传递也就是地址传递需要注意的地方,引起的bug
  6. * <p>
  7. * Created by lxk on 2017/3/23
  8. */
  9. public class Bug2 {
  10. public static void main(String[] args) {
  11. Student student1 = new Student();
  12. Car car = new Car("oooo", 100);
  13. student1.setCar(car);
  14. student1.setName("lxk");
  15. //克隆完之后,student1和student2应该没关系的,修改student1不影响student2的值,但是完之后发现,你修改car的值,student2也受影响啦。
  16. Student student2 = student1.clone();
  17. System.out.println("学生2:" + student2);//先输出student2刚刚克隆完之后的值,然后在修改student1的相关引用类型的属性值(car)和基本属性值(name)
  18. car.setSign("X5");
  19. student1.setName("xxx");
  20. System.out.println("学生2:" + student2);//再次输出看修改的结果
  21. }
  22. }

之后就该是执行的结果图了:

对上面执行结果的疑惑,以及解释说明:

我们可能觉得自己在bean里面实现clone接口,重写了这个clone方法,那么学生2是经由学生1clone,复制出来的,
那么学生1和学生2,应该是毫不相干的,各自是各自,然后,在修改学生1的时候,学生2是不会受影响的。

但是结果,不尽人意。从上图执行结果可以看出来,除了名字,这个属性是没有被学生1影响,关于car的sign属性已经因为学生1的变化而变化,这不是我希望的结果。

可见,这个简单的克隆实现也仅仅是个“浅克隆”,也就是基本类型数据,他是会给你重新复制一份新的,但是引用类型的,他就不会重新复制份新的。引用类型包括,上面的其他bean的引用,list集合,等一些引用类型。

那么怎么实现深克隆呢?

对上述代码稍作修改,如下:
学生bean的clone重写方法如下所示:

  1. @Override
  2. public Student clone() {
  3. Student student = null;
  4. try {
  5. student = (Student) super.clone();
  6. if (car != null) {
  7. student.setCar(car.clone());
  8. }
  9. } catch (CloneNotSupportedException ignored) {
  10. System.out.println(ignored.getMessage());
  11. }
  12. return student;
  13. }

然后还要Car类实现cloneable接口,复写clone方法:

  1. @Override
  2. public Car clone() {
  3. Car car = null;
  4. try {
  5. car = (Car) super.clone();
  6. if (myDog != null) {
  7. car.setMyDog(Lists.newArrayList(myDog));
  8. }
  9. if (boys != null) {
  10. car.setBoys(Lists.newArrayList(boys));
  11. }
  12. } catch (CloneNotSupportedException ignored) {
  13. System.out.println(ignored.getMessage());
  14. }
  15. return car;
  16. }

主测试代码不动,这个时候的执行结果如下:

可以看到,这个时候,你再修改学生1的值,就不会影响到学生2的值,这才是真正的克隆,也就是所谓的深克隆。

怎么举一反三?

可以看到,这个例子里面的引用类型就一个Car类型的属性,但是实际开发中,除了这个引用其他bean类型的属性外,可能还要list类型的属性值用的最多。

那么要怎么深克隆呢,就像我在Car bean类里面做的那样,把所有的引用类型的属性,都在clone一遍。那么你在最上层调用这个clone方法的时候,他就是真的深克隆啦。

我代码里面那么判断是为了避免空指针异常。当然,这个你也得注意咯。

注意 重写clone方法的时候,里面各个属性的null的判断哦。

上面的是override clone()方法来实现深克隆的。如果你这个要克隆的对象很复杂的话,你就不得不去每个引用到的对象去复写这个clone方法,这个太啰嗦来,改的地方,太多啦。

还有个方法就是使用序列化来实现这个深拷贝

  1. /**
  2. * 对象的深度克隆,此处的对象涉及Collection接口和Map接口下对象的深度克隆
  3. * 利用序列化和反序列化的方式进行深度克隆对象
  4. *
  5. * @param object 待克隆的对象
  6. * @param <T> 待克隆对象的数据类型
  7. * @return 已经深度克隆过的对象
  8. */
  9. public static <T extends Serializable> T deepCloneObject(T object) {
  10. T deepClone = null;
  11. ByteArrayOutputStream baos = null;
  12. ObjectOutputStream oos = null;
  13. ByteArrayInputStream bais = null;
  14. ObjectInputStream ois = null;
  15. try {
  16. baos = new ByteArrayOutputStream();
  17. oos = new ObjectOutputStream(baos);
  18. oos.writeObject(object);
  19. bais = new ByteArrayInputStream(baos
  20. .toByteArray());
  21. ois = new ObjectInputStream(bais);
  22. deepClone = (T)ois.readObject();
  23. } catch (IOException | ClassNotFoundException e) {
  24. e.printStackTrace();
  25. } finally {
  26. try {
  27. if(baos != null) {
  28. baos.close();
  29. }
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. try {
  34. if(oos != null) {
  35. oos.close();
  36. }
  37. } catch (IOException e) {
  38. e.printStackTrace();
  39. }
  40. try{
  41. if(bais != null) {
  42. bais.close();
  43. }
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. try{
  48. if(ois != null) {
  49. ois.close();
  50. }
  51. } catch (IOException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. return deepClone;
  56. }

具体的使用如下:

  1. /**
  2. * 使用序列化来实现深拷贝简单。但是,所涉及到的所有对象都的实现序列化接口。
  3. */
  4. private static void cloneBySerializable() {
  5. Student student1 = new Student();
  6. Car car = new Car("oooo", 100, Lists.newArrayList(new Dog("aaa", true, true)));
  7. student1.setCar(car);
  8. student1.setName("lxk");
  9. Student student2 = deepCloneObject(student1);
  10. System.out.println("学生2:" + student2);
  11. car.setSign("X5");
  12. car.setMyDog(null);
  13. student1.setName("xxx");
  14. System.out.println("学生2:" + student2);
  15. }

实现的效果,还是和上面的一样的,但是这个就简单多来,只需要给涉及到的每个引用类型,都去实现序列化接口就好啦。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对w3xue的支持。如果你想了解更多相关内容请查看下面相关链接

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号