首页 > 编程语言 >死磕Java面试系列:深拷贝与浅拷贝的实现原理

死磕Java面试系列:深拷贝与浅拷贝的实现原理

时间:2022-11-07 11:22:10浏览次数:45  
标签:Java name 对象 面试 Job user 拷贝 public

深拷贝与浅拷贝的问题,也是面试中的常客。虽然大家都知道两者表现形式不同点在哪里,但是很少去深究其底层原理,也不知道怎么才能优雅的实现一个深拷贝。其实工作中也常常需要实现深拷贝,今天一灯就带大家一块深入剖析一下深拷贝与浅拷贝的实现原理,并手把手教你怎么优雅的实现深拷贝。

1. 什么是深拷贝与浅拷贝

浅拷贝: 只拷贝栈内存中的数据,不拷贝堆内存中数据。

深拷贝: 既拷贝栈内存中的数据,又拷贝堆内存中的数据。

2. 浅拷贝的实现原理

由于浅拷贝只拷贝了栈内存中数据,栈内存中存储的都是基本数据类型,堆内存中存储了数组、引用数据类型等。

image

使用代码验证一下:

想要实现clone功能,需要实现 Cloneable 接口,并重写 clone 方法。

  1. 先创建一个用户类
// 用户的实体类,用作验证
public class User implements Cloneable {
    private String name;
    
    // 每个用户都有一个工作
    private Job job;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Job getJob() {
        return job;
    }

    public void setJob(Job job) {
        this.job = job;
    }


    @Override
    public User clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        return user;
    }
}
  1. 再创建一个工作类
// 工作的实体类,并没有实现Cloneable接口
public class Job {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
  1. 测试浅拷贝
/**
 * @author 一灯架构
 * @apiNote Java浅拷贝示例
 **/
public class Demo {

    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
        User user1 = new User();
        user1.setName("一灯架构");
        Job job1 = new Job();
        job1.setContent("开发");
        user1.setJob(job1);

        // 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
        User user2 = user1.clone();
        user2.setName("张三");
        Job job2 = user2.getJob();
        job2.setContent("测试");
        
        // 3. 输出结果
        System.out.println("user原对象= " + user1);
        System.out.println("user拷贝对象= " + user2);
    }

}

输出结果:

user原对象= {"name":"一灯架构","job":{"content":"测试"}}
user拷贝对象= {"name":"张三","job":{"content":"测试"}}

从结果中可以看出,对象拷贝把name修改为”张三“,原对象并没有变,name是String类型,是基本数据类型,存储在栈内存中。对象拷贝了一份新的栈内存数据,修改并不会影响原对象。

然后对象拷贝把Job中content修改为”测试“,原对象也跟着变了,原因是Job是引用类型,存储在堆内存中。对象拷贝和原对象指向的同一个堆内存的地址,所以修改会影响到原对象。

3. 深拷贝的实现原理

深拷贝是既拷贝栈内存中的数据,又拷贝堆内存中的数据。

image

实现深拷贝有很多种方法,下面就详细讲解一下,看使用哪种方式更方便快捷。

3.1 实现Cloneable接口

通过实现Cloneable接口来实现深拷贝是最常见的。

想要实现clone功能,需要实现Cloneable接口,并重写clone方法。

  1. 先创建一个用户类
// 用户的实体类,用作验证
public class User implements Cloneable {
    private String name;
    
    // 每个用户都有一个工作
    private Job job;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Job getJob() {
        return job;
    }

    public void setJob(Job job) {
        this.job = job;
    }


    @Override
    public User clone() throws CloneNotSupportedException {
        User user = (User) super.clone();
        // User对象中所有引用类型属性都要执行clone方法
        user.setJob(user.getJob().clone());
        return user;
    }
}
  1. 再创建一个工作类
// 工作的实体类,需要实现Cloneable接口
public class Job implements Cloneable {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    protected Job clone() throws CloneNotSupportedException {
        return (Job) super.clone();
    }
}
  1. 测试浅拷贝
/**
 * @author 一灯架构
 * @apiNote Java深拷贝示例
 **/
public class Demo {

    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
        User user1 = new User();
        user1.setName("一灯架构");
        Job job1 = new Job();
        job1.setContent("开发");
        user1.setJob(job1);

        // 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
        User user2 = user1.clone();
        user2.setName("张三");
        Job job2 = user2.getJob();
        job2.setContent("测试");
        
        // 3. 输出结果
        System.out.println("user原对象= " + user1);
        System.out.println("user拷贝对象= " + user2);
    }

}

输出结果:

user原对象= {"name":"一灯架构","job":{"content":"开发"}}
user拷贝对象= {"name":"张三","job":{"content":"测试"}}

从结果中可以看出,user拷贝对象修改了name属性和Job对象中内容,都没有影响到原对象,实现了深拷贝。

通过实现Cloneable接口的方式来实现深拷贝,是Java中最常见的实现方式。

缺点是: 比较麻烦,需要所有实体类都实现Cloneable接口,并重写clone方法。如果实体类中新增了一个引用对象类型的属性,还需要添加到clone方法中。如果继任者忘了修改clone方法,相当于挖了一个坑。

3.2 使用JSON字符串转换

实现方式就是:

  1. 先把user对象转换成json字符串
  2. 再把json字符串转换成user对象

这是个偏方,但是偏方治大病,使用起来非常方便,一行代码即可实现。

下面使用fastjson实现,使用Gson、Jackson也是一样的:

import com.alibaba.fastjson.JSON;


/**
 * @author 一灯架构
 * @apiNote Java深拷贝示例
 **/
public class Demo {

    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建用户对象,{"name":"一灯架构","job":{"content":"开发"}}
        User user1 = new User();
        user1.setName("一灯架构");
        Job job1 = new Job();
        job1.setContent("开发");
        user1.setJob(job1);

        //// 2. 拷贝用户对象,name修改为"张三",工作内容修改"测试"
        User user2 = JSON.parseObject(JSON.toJSONString(user1), User.class);
        user2.setName("张三");
        Job job2 = user2.getJob();
        job2.setContent("测试");

        // 3. 输出结果
        System.out.println("user原对象= " + JSON.toJSONString(user1));
        System.out.println("user拷贝对象= " + JSON.toJSONString(user2));
    }

}

输出结果:

user原对象= {"name":"一灯架构","job":{"content":"开发"}}
user拷贝对象= {"name":"张三","job":{"content":"测试"}}

从结果中可以看出,user拷贝对象修改了name属性和Job对象中内容,并没有影响到原对象,实现了深拷贝。

3.3 集合实现深拷贝

再说一下Java集合怎么实现深拷贝?

其实非常简单,只需要初始化新对象的时候,把原对象传入到新对象的构造方法中即可。

以最常用的ArrayList为例:

/**
 * @author 一灯架构
 * @apiNote Java深拷贝示例
 **/
public class Demo {

    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原对象
        List<User> userList = new ArrayList<>();

        // 2. 创建深拷贝对象
        List<User> userCopyList = new ArrayList<>(userList);
    }

}

我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见

image

标签:Java,name,对象,面试,Job,user,拷贝,public
From: https://www.cnblogs.com/yidengjiagou/p/16865368.html

相关文章

  • 使用VSCode,学习JAVA Hello World
    使用VSCode,学习JAVA安装插件:ExtensionPackforJava安装好后ctrl+shift+p,输入Java,选择“创建java项目”,选择项目类型:Nobuildtools,创建一个HelloWorld项目 ......
  • 面试官最喜欢问的几个react相关问题
    除了在构造函数中绑定this,还有其它方式吗你可以使用属性初始值设定项(propertyinitializers)来正确绑定回调,create-react-app也是默认支持的。在回调中你可以使用箭头......
  • Java中Number下各数据类型的范围说明
    转自:http://www.java265.com/JavaCourse/202111/1792.html下文笔者将着重讲述java基础知识,Number类型包含的类型简介说明,如下所示:Number类型有以下6种类型Byte类型......
  • JAVA代码覆盖率工具JaCoCo
    一、代码覆盖率统计工具的能与不能能:代码覆盖率统计工具能用来发现没有被测试(单元测试、接口自动化测试、ui自动化测试、手工测试等)覆盖的代码。1、测试中未覆盖的代码......
  • 高频Fiddler软件测试面试题
    在软件测试的面试过程中,Fiddler抓包工具的相关知识,可以说是必考的了,下面为大家整理了一些高频Fiddler面试题,拿走不谢~1、Fiddler弱网测试的原理是什么?Fiddler来模拟限速,是因......
  • Vue面试题44:Vue3为什么要用Proxy代替defineProperty?(总结自B站up主‘前端杨村长’视频,
    分析Vue3中最重大的更新之一就是响应式模块reactivity的重写。主要就是用Proxy替换`defineProperty实现响应式;此变化主要是从性能方面考量;思路属性拦截的几种方......
  • Ubuntu系统中CUDA套件nvvp启动后报错Unable to make protected void java.net.URLClas
    最近在看cuda方面的内容,需要对cuda代码做一些性能分析,于是需要使用nvvp,但是启动nvvp后报错:Causedby:java.lang.reflect.InaccessibleObjectException:Unabletomakepr......
  • Java基础
    jdk在oracle官网下载,免费注册就可下载 001二进制转换    002计算机存储 1Byte=8bit  1KB=1024Byte  1MB=1024KB 1GB=1024MB 1TB=102......
  • Java函数式编程(2):流式计算
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ Lambda表达式虽然将接口作为代码块赋值给了变量,但如果仅仅只是Lambda表达式,还无法让Java由量变引起质变。真正让Lambda......
  • java创建文件对象相关构造器和方法
     1、什么是文件?文件是我们保存数据的地方。2、文件流文件在程序中是以流的形式来操作的。流:数据在数据源(文件)和程序(内存)之间经历的路径输入流:数据从数据源(文件)......