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()为浅拷贝

只要自己变优秀了,其他的事情才会跟着好起来(少将2)

java提高(15)---java深浅拷贝的更多相关文章

  1. Java提高篇——Java实现多重继承

    多重继承指的是一个类可以同时从多于一个的父类那里继承行为和特征,然而我们知道Java为了保证数据安全,它只允许单继承.有些时候我们会认为如果系统中需要使用多重继承往往都是糟糕的设计,这个时候我们往往需 ...

  2. linux(centos8):安装java jdk 15 (java 15)

    一,下载jdk15 官方网站: https://www.oracle.com/java/ 下载页面: https://www.oracle.com/cn/java/technologies/javas ...

  3. Java提高篇——Java 异常处理

    异常的概念 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error:如果你用Syst ...

  4. 【python之路15】深浅拷贝及函数

    一.集合数据类型(set):无序不重复的集合,交集.并集等功能 二.三元运算符 三.深浅拷贝 1)字符串和数字:深浅内存地址都一样 2)其他:浅拷贝:仅复制最外面第一层 深拷贝:除了最内层其他均拷贝 ...

  5. 【java提高】---java反射机制

    java反射机制 一.概述 1.什么是反射机制 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态 ...

  6. Java总结——常见Java集合实现细节(1)

    Java提高——常见Java集合实现细节(1) 2018年04月18日 15:07:35 阅读数:25 集合关系图 Set和Map set代表一种集合元素无序.集合元素不可重复的集合 map代表一种由 ...

  7. python基础知识9---字符串拼接,深浅拷贝,三元运算

    一.字符串格式化 Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-3 ...

  8. java 的对象拷贝(有深浅拷贝两种方式,深拷贝实现的两种方式(逐层实现cloneable接口,序列化的方式来实现))

    Java提高篇--对象克隆(复制)(转自:http://www.cnblogs.com/Qian123/p/5710533.html#_label0)   阅读目录 为什么要克隆? 如何实现克隆 浅克 ...

  9. 关于Java的Object.clone()方法与对象的深浅拷贝

    文章同步更新在个人博客:关于Java的Object.clone()方法与对象的深浅拷贝 引言 在某些场景中,我们需要获取到一个对象的拷贝用于某些处理.这时候就可以用到Java中的Object.clon ...

随机推荐

  1. 创建servlet的三种方式

    第一种方式,实现Servlet接口 package com.example.servlet; import java.io.IOException; import javax.servlet.Serv ...

  2. CSS弹性盒子的基本用法

    .container { width: 302px; height: 302px; border: 1px solid; display: flex } .item { width: 100px; h ...

  3. 加密原理介绍,代码实现DES、AES、RSA、Base64、MD5

    阅读目录 github下载地址 一.DES对称加密 二.AES对称加密 三.RSA非对称加密 四.实际使用 五.关于Padding 关于电脑终端Openssl加密解密命令 关于网络安全的数据加密部分, ...

  4. java thread yield 的设计目的是什么?

    如题,java thread yield 的设计目的是什么?有什么实际应用场景吗? Ps:它的作用是理解的,和 join 等的区别也理解.就是个人感觉这个设计有点鸡肋(可能是个人读书太少...) It ...

  5. RedisTemplate执行Redis脚本

    对于Redis脚本使用过的同学都知道,这个主要是为了防止竞态条件而用的.因为脚本是顺序执行的.(不用担心效率问题)比如我在工作用,用来设置考试最高分. 如果还没有用过的话,先去看Redis脚本的介绍, ...

  6. 3.python元类编程

     1.1.propety动态属性 在面向对象编程中,我们一般把名词性的东西映射成属性,动词性的东西映射成方法.在python中他们对应的分别是属性self.xxx和类方法.但有时我们需要的属性需要根据 ...

  7. SSM-SpringMVC-33:SpringMVC中拦截器Interceptor讲解

     ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 拦截器Interceptor: 对处理方法进行双向的拦截,可以对其做日志记录等 我选择的是实现Handler ...

  8. Linux网络简介

    Linux的网络简介 1.TCP/IP协议簇  (通俗的解释就是为了网路传输指定的一种标准),它将网络划分为四层:应用层 .传输层.互联层 .网络接口层 . 基础层是网络接口层(说白了就是把帧格式的数 ...

  9. JavaScript设计模式Item 1—多态

    多态的实际含义是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果.换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈. 从字面上来理解多态不太容易, ...

  10. RabbitMQ (四) 路由选择 (Routing)

    上一篇博客我们建立了一个简单的日志系统,我们能够广播日志消息给所有你的接收者,如果你不了解,请查看:RabbitMQ (三) 发布/订阅.本篇博客我们准备给日志系统添加新的特性,让日志接收者能够订阅部 ...