Java中浅拷贝和深拷贝

前言

我看好多java面试中发现有蛮多多深/浅拷贝的文章、看别人的文章最大的问题就是看是看了、但是看进去多少又是一回事、所以还是动动手码两行!!!

例子

  • 假设创建一个对象A然后在将A对象赋值给B、此时你操作了B对象等同于你操作了A对象、原因就是因为他们的内存地址指向的是同一块区域、可以叫做引用的拷贝
    User user1 = new User();
    User user2 = user1;
    
    // 输出结果
    user1地址:demo4.User@7f31245a
    user2地址:demo4.User@7f31245a
    复制代码

简单看一下这个

  public class User implements Cloneable{   // 实现Cloneable接口
  
    @Override   // 从写Object类中的clone()方法
    protected Object clone() throws CloneNotSupportedException {
      return super.clone();  // 主要还是调用Object类中的clone()方法
    }
  }
复制代码
  • 拷贝的共同目的就是为了拷贝一个全新的对象。

浅拷贝(Shallow Copy)

简单理解一下、顾名思义、浅拷贝就是浅浅的拷贝、哈哈哈绝了、有没有被内涵到!!!话不多说先上代码

案例:

注意:下面实体类使用的Lombok生成get/set/构造等一些方法

Hobby类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hobby{
    private String hobbyName;
    
    // 此处省略get/set/构造/toString方法
}
复制代码

User类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Cloneable{
    private String name;
    private Integer age;
    private Hobby hobby;

    // 此处省略get/set/构造/toString方法

    // 从写clone方法()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

复制代码

测试类

public class Test2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Hobby hobby = new Hobby("唱歌");
        User user1 = new User("小明",18,hobby);
        User user2 = (User) user1.clone();
        System.out.println("============初始内容==========");
        System.out.println("user1地址:"+user1.toAddress()+"\t\tuser2地址:"+user2.toAddress());
        System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
                "user2的Hobby地址:"+user2.getHobby().toAddress());
        System.out.println("user1:"+user1.toString());
        System.out.println("user2:"+user2.toString());
        user2.setName("小红");
        user2.setAge(17);
        hobby.setHobbyName("跳舞");
        user2.setHobby(hobby);
        System.out.println("============值赋完内容==========");
        System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
                "user2的Hobby地址:"+user2.getHobby().toAddress());
        System.out.println("user1的Hobby地址:"+user1.toString());
        System.out.println("user2的Hobby地址:"+user2.toString());

    }
}

复制代码

测试结果:

============初始内容==========
user1地址:demo4.User@7f31245a		user2地址:demo4.User@6d6f6e28
user1的Hobby地址:demo4.Hobby@135fbaa4		user2的Hobby地址:demo4.Hobby@135fbaa4
user1:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
user2:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
============值赋完内容==========
user1的Hobby地址:demo4.Hobby@135fbaa4		user2的Hobby地址:demo4.Hobby@135fbaa4
user1的Hobby地址:User{name='小明', age=18, Hobby=ID{hobbyName=跳舞}}
user2的Hobby地址:User{name='小红', age=17, Hobby=ID{hobbyName=跳舞}}
复制代码

个人小结:

  • 浅拷贝简单来说就是将一个对象new了两次、但是和浅拷贝性质不一样、浅拷贝实际意义是将上一个对象拷贝一份、将其对象数据拷贝到新地址中、而最后达到两个对象的引用地址不一样。
  • 浅拷贝的特点
    • 对应基本数据类型的成员变量: 基础类型的拷贝,其中一个对象修改该值,不会影响另外一个、原因是 因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象 。
    • 对应引用类型的成员变量:比如数组或者类对象、改变其中一个,会对另外一个也产生影响 、原因是因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。

弊端:浅拷贝只对本身要拷贝的对象作为拷贝、而不会将拷贝对象中的引用类型属性再次进行拷贝。

图解

image.png

深拷贝 (Deep Copy)

简单理解一下、那你肯定说和上面浅拷贝是差不多的意思咯:深拷贝就是深深的拷贝、其实这个不能这么理解、先上代码!!!

案例:

Hobby类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hobby implements Cloneable{
    private String hobbyName;
    
    // 从写拷贝方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
复制代码

User类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Cloneable{
    private String name;
    private Integer age;
    private Hobby hobby;
    
     // 从写拷贝方法、有所改变
    @Override
    protected Object clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        this.hobby = (Hobby)user.getHobby().clone();
        return user;
    }
}

复制代码

测试类

public class Test2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Hobby hobby = new Hobby("唱歌");
        User user1 = new User("小明",18,hobby);
        User user2 = (User) user1.clone();
        System.out.println("============初始内容==========");
        System.out.println("user1地址:"+user1.toAddress()+"\t\tuser2地址:"+user2.toAddress());
        System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
                "user2的Hobby地址:"+user2.getHobby().toAddress());
        System.out.println("user1:"+user1.toString());
        System.out.println("user2:"+user2.toString());
        user2.setName("小红");
        user2.setAge(17);
        hobby.setHobbyName("跳舞");
        user2.setHobby(hobby);
        System.out.println("============值赋完内容==========");
        System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
                "user2的Hobby地址:"+user2.getHobby().toAddress());
        System.out.println("user1的Hobby地址:"+user1.toString());
        System.out.println("user2的Hobby地址:"+user2.toString());

    }
}
复制代码

测试结果

============初始内容==========
user1地址:demo4.User@7f31245a		user2地址:demo4.User@6d6f6e28
user1的Hobby地址:demo4.Hobby@135fbaa4		user2的Hobby地址:demo4.Hobby@45ee12a7
user1:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
user2:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
============值赋完内容==========
user1的Hobby地址:demo4.Hobby@135fbaa4		user2的Hobby地址:demo4.Hobby@45ee12a7
user1的Hobby地址:User{name='小明', age=18, Hobby=ID{hobbyName=唱歌}}
user2的Hobby地址:User{name='小红', age=17, Hobby=ID{hobbyName=跳舞}}
复制代码

个人小结:

  • 深拷贝除了拷贝对象本身、对象内部的属性引用也会进行拷贝、唯一麻烦的就是在对象内部的属性的对象要去实现Cloneable接口、还要从写clone()方法、而本身对象的clone()方法中要对属性引用对象调用clone()方法将其赋值给这个引用对象属性
  • 深拷贝的特点
    • 对应基本数据类型的成员变量: 基础类型的拷贝,其中一个对象修改该值,不会影响另外一个、原因是 因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象 。(和浅拷贝一样)
    • 对应引用类型的成员变量:比如数组或者类对象、改变其中一个、不会对另外一个也产生影响 、原因是因为深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间 。
  • 弊端
    • 如有对象属性是一个对象、就非常麻烦、需要每个对象实现Cloneable接口、并从写clone()方法、并在上一个对象的clone()方法中去调用属性对象的clone()将其拷贝后赋值给属性。
    • 深拷贝的开销大

图解

image.png

浅拷贝和深拷贝总结

  • 不管还是浅拷贝还是深拷贝、它们两的作用就是复制对象、复制后的对象和被复制的对象引用地址不一样、可以简单理解为将复制的对象放在一个新的内存区域中。
  • 浅拷贝可以复制对象的基本类型和引用类型、只不过引用类型指向的同一片内存空间
  • 深拷贝可以复制对象的基本类型和引用类型、只不过引用类型指向的不是同一片内存空间

参考: www.jianshu.com/p/94dbef2de…

采用序列化对象的形式来实现深度克隆

什么是序列化、序列化就是将内存中的对象保存在硬盘中、当做一个文件、然后将文件读取出来变成内存中的对象、这个形式是反序列化。

@Test
public void fun_5() throws Exception {
    /**
     *  注意:
     *      User对象需要实现Serializable接口
     */
    Hobby hobby = new Hobby();
    User user1 = new User("小明",18,hobby);  // 创建新对象
    String projectPath = System.getProperty("user.dir");  // 获取当前项目路径
    System.out.println(projectPath);
    File f = new File(projectPath+"/obj.txt"); // 文件报存的本地地址
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); // 输出流
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));// 输入流

    oos.writeObject(user1);  // 将对象序列化

    User user2 = (User) ois.readObject();// 将对象反序列化

    System.out.println("user1地址:"+user1.toAddress()+"\t\tuser2地址:"+user2.toAddress());
    System.out.println("user1的Hobby地址:"+user1.getHobby().toAddress()+"\t\t" +
            "user2的Hobby地址:"+user2.getHobby().toAddress());

    ois.close(); // 释放资源
    oos.close(); // 释放资源
}
复制代码

对象(反)序列化、要实现Serializable接口、否则会报一个错误

java.io.NotSerializableException: pojo.User
复制代码

测试结果

user1地址:demo4.User@5fdef03a		user2地址:demo4.User@2c13da15
user1的Hobby地址:demo4.Hobby@5ccd43c2		user2的Hobby地址:demo4.Hobby@77556fd
复制代码

具体理解还请自行测试。

Tips

文章还是要看、没事就码两行吧、生活不宜、别老想拿多少多少K、天天念叨我会羡慕的!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享