• 浅拷贝和深拷贝?仔细搞懂你


    1.序言

    在java语言中,如果需要拷贝一个对象时,分两种类型的拷贝:浅拷贝深拷贝

     在拷贝的同时又不想改变原有对象的属性值 ,就只能是深拷贝      

    两者的区别如下:

    • 浅拷贝:只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。

    • 深拷贝:只是拷贝了源对象的值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。

    2.浅拷贝

    浅拷贝示例如下:

    1. public static void main(String[] args) {
    2. Big big = new Big("1", new Address("path1"));
    3. Big big3 = big;
    4. big3.setStart("3");
    5. big3.getAddress().setPath("path3");
    6. System.out.println(big);
    7. System.out.println(big3);
    8. }
    9. @Data
    10. @AllArgsConstructor
    11. @NoArgsConstructor
    12. class Big {
    13. private String start;
    14. private Address address;
    15. }

    执行结构如下:

    Big(start=3, address=Address(path=path3))
    Big(start=3, address=Address(path=path3))

    可以清晰的发现拷贝对象big3,在设置新值的时候,改变了原有被拷贝对象的值,这就是典型的浅拷贝

    3.深拷贝

    深拷贝经过总结,常见的有6种实现方法

    • 构造方法
    • 重写clone方法
    • Apache Commonss Lang序列化
    • Gson序列化
    • Jackson序列化
    • 自定义序列化

     

    3.1.构造方法

    通过在调用构造函数进行深拷贝,形参如果是基本类型和字符串则直接赋值,如果是对象则重新new一个。

    1. Big big = new Big("1", new Address("path1"));
    2. Big big2 = new Big("2", new Address("path2"));

    优缺点上图所示,比较常见的用法,但是局限性很大

    3.2.实现Cloneable接口,重写Clone方法

    这个地方需要注意三个点

    • 需要实现Cloneable接口,重写Clone方法,包括被拷贝的对象和对象中的引用对象,例如Test和Address
    • 引用对象属性需要重新赋值,例如  test.address = address.clone();
    • 通过原有对象的clone方法生成新对象,例如  Test test2 = test.clone();
    1. public static void main(String[] args) {
    2. Test test = new Test("1", new Address("path1"));
    3. Test test2 = test.clone();
    4. test2.setStart("2");
    5. test2.getAddress().setPath("path2");
    6. System.out.println(test);
    7. System.out.println(test2);
    8. }
    9. @Data
    10. @AllArgsConstructor
    11. @NoArgsConstructor
    12. class Test implements Cloneable {
    13. private String start;
    14. private Address address;
    15. @Override
    16. protected Test clone(){
    17. Test test = null;
    18. try {
    19. test = (Test) super.clone();
    20. test.address = address.clone();
    21. } catch (CloneNotSupportedException e) {
    22. e.printStackTrace();
    23. }
    24. return test;
    25. }
    26. }
    27. @Data
    28. @AllArgsConstructor
    29. @NoArgsConstructor
    30. class Address implements Cloneable{
    31. private String path;
    32. @Override
    33. protected Address clone() {
    34. Address address = null;
    35. try {
    36. address = (Address) super.clone();
    37. } catch (CloneNotSupportedException e) {
    38. e.printStackTrace();
    39. }
    40. return address;
    41. }
    42. }

    3.3.Apache序列化

    Java提供了序列化的能力,可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类需要实现Serializable接口。Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。

    需要注意三个点:

    • 实现Serializable接口,包括被拷贝的对象和对象中的引用对象,例如Demo和Address
    • 引入Apache第三方Jar包
    • 生成方式:Demo demo2 = SerializationUtils.clone(demo);
    1. public static void main(String[] args) {
    2. Demo demo = new Demo("1", new Address("path1"));
    3. Demo demo2 = SerializationUtils.clone(demo);
    4. demo2.setStart("2");
    5. demo2.getAddress().setPath("path2");
    6. System.out.println(demo);
    7. System.out.println(demo2);
    8. }
    9. @Data
    10. @AllArgsConstructor
    11. @NoArgsConstructor
    12. class Demo implements Serializable {
    13. private String start;
    14. private Address address;
    15. }
    16. @Data
    17. @AllArgsConstructor
    18. @NoArgsConstructor
    19. class Address implements Serializable{
    20. private String path;
    21. @Override
    22. protected Address clone() {
    23. Address address = null;
    24. try {
    25. address = (Address) super.clone();
    26. } catch (CloneNotSupportedException e) {
    27. e.printStackTrace();
    28. }
    29. return address;
    30. }
    31. }

    3.4.Gson序列化

    Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。

    需要注意两个点:

    • 引入Google第三方Jar包
    • 生成方式:
      Gson gson = new Gson();
      Big big2 = gson.fromJson(gson.toJson(big), Big.class);

    
        com.google.code.gson
        gson
        2.9.0
    

    1. public static void main(String[] args) {
    2. Big big = new Big("1", new Address("path1"));
    3. Gson gson = new Gson();
    4. Big big2 = gson.fromJson(gson.toJson(big), Big.class);
    5. big2.setStart("2");
    6. big2.getAddress().setPath("path2");
    7. System.out.println(big);
    8. System.out.println(big2);
    9. }
    10. @Data
    11. @AllArgsConstructor
    12. @NoArgsConstructor
    13. class Big {
    14. private String start;
    15. private Address address;
    16. }

    3.5.Jackson序列化

    Jackson与Gson相似,可以将对象序列化成JSON。不过拷贝的类需要有默认的无参构造函数。这里不再细说了。

    1. public static void main(String[] args) {
    2. Food food1 =new Food("百事可乐","冰");
    3. //拷贝
    4. ObjectMapper objectMapper = new ObjectMapper();
    5. Food food2 =objectMapper.readValue(objectMapper.writeValueAsString(food1), Food.class));
    6. food2.setType("常温");
    7. }

    3.6.自定义序列化

    在对象类中自定义方法,实现序列化和反序列化

    需要注意的是

    • 实现Serializable接口,包括被拷贝的对象和对象中的引用对象
    • 需要增加自定义的deepClone方法,包括被拷贝的对象和对象中的引用对象

    1. @Data
    2. @AllArgsConstructor
    3. @NoArgsConstructor
    4. class Demo implements Serializable {
    5. private String start;
    6. private Address address;
    7. public Object deepClone(){
    8. ByteArrayInputStream bis=null;
    9. ObjectInputStream ois=null;
    10. ByteArrayOutputStream bos=null;
    11. ObjectOutputStream oos=null;
    12. try {
    13. //序列化
    14. bos=new ByteArrayOutputStream();
    15. ObjectOutputStream objectOutputStream = oos = new ObjectOutputStream(bos);
    16. oos.writeObject(this);
    17. //反序列化
    18. bis=new ByteArrayInputStream(bos.toByteArray());
    19. ois=new ObjectInputStream(bis);
    20. Person p= (Person) ois.readObject();
    21. return p;
    22. } catch (IOException e) {
    23. e.printStackTrace();
    24. } catch (ClassNotFoundException e) {
    25. e.printStackTrace();
    26. } finally {
    27. try {
    28. bos.close();
    29. oos.close();
    30. bis.close();
    31. ois.close();
    32. } catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. }
    36. return null;
    37. }
    38. }

    总结:推荐Apache和Gson方式,毕竟都是常用的工具类,代码契合度高

  • 相关阅读:
    nodejs采集淘宝、天猫网商品详情数据以及解决_m_h5_tk令牌及sign签名验证(2023-09-09)
    基于51单片机的全自动智能洗衣机控制系统Proteus仿真
    7款最佳的图片编辑App
    如何创建springboot项目
    java实战——图书管理项目
    Hadoop运行模式
    PAT A1007 Maximum Subsequence Sum
    Radius OTP完成堡垒机登录认证 安当加密
    Docker命令
    无需公网IP,实现外网远程访问管家婆ERP进销存系统的方法
  • 原文地址:https://blog.csdn.net/weixin_42211693/article/details/126298791