java提高(15)---java深浅拷贝

简介: java提高(15)---java深浅拷贝 一、前言 为什么会有深浅拷贝这个概念? 我觉得主要跟JVM内存分配有关,对于基本数据类型,只存在栈内存,所以它的拷贝不存在深浅拷贝这个概念。而对于对象而言,一个对象的创建会在内存中分配两块空间,一个在栈内存存对象的引用指针,一个在堆内存存放对象。

java提高(15)---java深浅拷贝

一、前言

为什么会有深浅拷贝这个概念?

我觉得主要跟JVM内存分配有关,对于基本数据类型,只存在栈内存,所以它的拷贝不存在深浅拷贝这个概念。而对于对象而言,一个对象的创建会在内存中分配两块空间,一个在栈内存存对象的引用指针,一个在堆内存存放对象。这个时候会有一个问题,你拷贝的只是这个引用指针还是拷贝两块内存一起拷贝,这个时候就会有深浅拷贝一说。
还有之前我认为Arrays.copyOf()是深度拷贝,亲测后发现原来它也是浅拷贝。下面进行具体说明。


二、数据类型

数据分为基本数据类型(int, boolean, double, byte, char等)和对象数据类型。
基本数据类型的特点:直接存储在栈(stack)中的数据.
引用数据类型的特点:在栈内存存储对象引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。


三、什么是浅拷贝和深拷贝

首先需要明白,深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。那先来看看浅拷贝和深拷贝的概念。
在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 =号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。

浅拷贝:如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象。

深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

深拷贝和浅拷贝的示意图大致如下:

具体接下来代码演示。


四、代码演示

1、浅拷贝

Person

public class Person {
    public String name;
    public Integer age;
    public String sex;
    /**
     * 提供get和set方法和全参构造函数
     */
}

Test

    public static void main(String[] args) throws Exception {
        Person person = new Person("小小",3,"女");
        //将person值赋值给person1
        Person person1 = person;
        System.out.println(person);
        System.out.println(person1);
        person1.setName("小小她爸");
        System.out.println("person 中 name为:"+person.getName());
        System.out.println("person1 中 name为:"+person.getName());
    }

查看运行结果

从图片中我们可以很明显看出,它们指向的内存地址是一致的,同样我改变person1的属性值时发现person的属性值也改变了。

说明:对于对象用"=" 赋值 其实只是引用指针的复制,这两个引用还是指向同一个对象。

2、深拷贝

如果要实现深拷贝就会比较复杂点

Student

/**
 * 如果对象要实现深拷贝 那么实体需要做两步
 * 1、实体实现Cloneable接口
 * 2、重写 clone()方法
 */
public class Student implements Cloneable {

    public String name;
    public Integer age;
    public String sex;
    //这也是个实体
    public Address address;
    /**
     * 提供get和set方法和全参构造函数
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Test

    public static void main(String[] args) throws Exception {
        Student student = new Student("小小", 3, "女", null);
        //将person值赋值给person1
        Student student1 = (Student) student.clone();
        System.out.println(student);
        System.out.println(student1);
        student1.setName("小小她爸");
        System.out.println("person 中 name为:" + student.getName());
        System.out.println("person1 中 name为:" + student1.getName());
        }

这里可以已经是两个不同的对象了。但是这里需要注意的是,如果对象中含有对象,这个对象还是浅拷贝。

Address

public class Address  {
    public String  city;
    public  int phone;
    /**
     * 提供get和set方法和全参构造函数
     */
    }

Test

    public static void main(String[] args) throws Exception {
        Address address = new Address("杭州", 1888888888);
        Student student2 = new Student("小小", 3, "女", address);
        //将person值赋值给person1
        Student student3 = (Student) student2.clone();
        address.setCity("北京天安门");
        System.out.println("person2 中 city为:" + student2.getAddress().getCity());
        System.out.println("person3 中 city为:" + student3.getAddress().getCity());

    }

我们发现虽然Student是实现了深拷贝,但Address却还是浅拷贝,那如何让Adress也实现深拷贝呢。
Address修改

public class Address implements Cloneable {
    public String  city;
    public  int phone;
   /**
     * 提供get和set方法和全参构造函数
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

Student修改

 //修改clone方法
   @Override
    protected Object clone() throws CloneNotSupportedException {
        Student s = (Student) super.clone();
        s.address = (Address) address.clone();
        return s;
    }

弊端: 这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,
那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
所以还有另一种实现深拷贝方法。

序列化实现深拷贝

//序列化实现深拷贝
public Object deepClone() throws Exception{
    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}
 //因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。


五、Arrays.copyOf()

之前我误以为Arrays.copyOf()为深拷贝,那只是因为我用的是基本数据类型作为数组,而基本数据类型上面已经说过它没有深浅拷贝这个概念,可以把他理解成只有深拷贝。

 public static void main(String[] args) {

        //1、基本数据类型
        int[] a = {0, 1, 2, 3};
        // Arrays.copyOf拷贝
        int[] copy = Arrays.copyOf(a, a.length);
        a[0] = 1;
        System.out.println(Arrays.toString(copy));
        System.out.println(Arrays.toString(a));

        //2、对象数组
        Student[]  stuArr = {new Student("小小", 3, "女"),new Student("小小爸", 29, "男"),new Student("小小妈", 27, "女")};
        // Arrays.copyOf拷贝
        Student[] copyStuArr = Arrays.copyOf(stuArr, stuArr.length);
        copyStuArr[0].setName("小小爷爷");
        System.out.println(Arrays.toString(stuArr));
        System.out.println(Arrays.toString(copyStuArr));
        

    }

运行结果:

可以明显看出,对于基本数据类型只有深拷贝,而对于数组对象而言,明显存在深浅拷贝,而且可以看出Arrays.copyOf()为浅拷贝

原文地址https://www.cnblogs.com/qdhxhz/p/10527245.html

相关文章
|
9月前
|
Java Apache
Java的深浅拷贝认识
该内容是关于Java中深拷贝和浅拷贝的解释。浅拷贝创建新对象,但属性(包括引用类型)仍指向原对象属性,导致修改时互相影响。深拷贝则为所有引用类型创建副本,确保对象独立。深拷贝适用于写入操作和需要独立副本的场景。分辨深浅拷贝主要看赋值和方法参数传递,以及是否有创建新实例的逻辑。
|
9月前
|
Java
【Java】数组中的拷贝方法与初步理解深浅拷贝
【Java】数组中的拷贝方法与初步理解深浅拷贝
56 0
|
Java
Java中的深浅拷贝问题,你清楚吗?
Java中的深浅拷贝问题,你清楚吗?
122 0
Java中的深浅拷贝问题,你清楚吗?
|
存储 JSON 缓存
5张图搞懂Java深浅拷贝
在开发、刷题、面试中,我们可能会遇到将一个对象的属性赋值到另一个对象的情况,这种情况就叫做拷贝。拷贝与Java内存结构息息相关,搞懂Java深浅拷贝是很必要的!
243 0
5张图搞懂Java深浅拷贝
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
35 14
|
5天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
34 13
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
109 17
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者