Java 拷贝,你能说出个 123 么?
作者 | sowhat1412 责编 | 张文
头图 | CSDN 下载自视觉中国
来源 | sowhat1412(ID:sowhat9094)
本文主要讲解:对象创建方式、Java中的深拷贝和浅拷贝。
创建对象的5种方式
1.1 通过 new 关键字
这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象。比如 Object obj = new Object()。
1.2 通过 Class 类的 newInstance() 方法
这种默认是调用类的无参构造方法创建对象。比如:
Person p2 = (Person) Class. forName("com.ys.test. Person"). newInstance();
1.3 通过 Constructor 类的 newInstance 方法
这和第二种方法类时,都是通过反射来实现。通过 java.lang.relect.Constructor 类的 newInstance() 方法指定某个构造器来创建对象。实际上第二种方法利用 Class 的 newInstance() 方法创建对象,其内部调用还是 Constructor 的 newInstance() 方法。
Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
1.4 利用 Clone 方法
Clone 是 Object 类中的一个方法,通过对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B,clone 克隆,顾名思义就是创建一个一模一样的对象出来。
Person p4 = (Person) p3.clone();
1.5 序列化
序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。
Java 基本复制方法
java赋值是复制对象引用,如果我们想要得到一个对象的==副本==,使用赋值操作是无法达到目的的:修改新对象的值会同时修改旧对象的值。
public class Client
{public static void main(String[] args) throws CloneNotSupportedException{Person person = new Person(15, "sowhat", new Address("河北", "建华南大街"));Person p1 = person;p1.setAge(45);System.out.println(p1.hashCode());System.out.println(person.hashCode());System.out.println("================");System.out.println(p1.display());System.out.println(person.display());}
}
Clone 方法
如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到java中对象的复制,如原生的clone()方法。本次讲解的是 Java 的深拷贝和浅拷贝,其实现方式正是通过调用 Object 类的 clone() 方法来完成。在 Object.class 类中,源码为:
/*** ...* performs a "shallow copy" of this object, not a "deep copy" operation.* 上面这里已经说明了,clone()方法是浅拷贝,而不是深拷贝* @see java.lang.Cloneable*/protected native Object clone() throws CloneNotSupportedException;
这是一个用 native 关键字修饰的方法。关于native关键字有一篇博客专门有介绍。不理解也没关系,只需要知道用 native 修饰的方法就是告诉操作系统。这个方法我不实现了,让操作系统去实现(参考JNI)。具体怎么实现我们不需要了解,只需要知道 clone方法的作用就是复制对象,产生一个新的对象。那么这个新的对象和原对象是==什么关系呢==?
基本类型和引用类型
这里再给大家普及一个概念,在 Java 中基本类型和引用类型的区别。在 Java 中数据类型可以分为两大类:基本类型和引用类型。
基本类型也称为值类型,分别是字符类型 char,布尔类型 boolean以及数值类型 byte、short、int、long、float、double。
引用类型则包括类、接口、数组、枚举等。
Java 将内存空间分为堆和栈。基本类型直接在栈 stack中存储数值,而引用类型是将引用放在栈中,实际存储的值是放在堆 heap中,通过栈中的引用指向堆中存放的数据。
上图定义的 a 和 b 都是基本类型,其值是直接存放在栈中的;而 c 和 d 是 String 声明的,这是一个引用类型,引用地址是存放在栈中,然后指向堆的内存空间。下面 d = c,这条语句表示将 c 的引用赋值给 d,那么 c 和 d 将指向同一块堆内存空间。
浅拷贝
接下来用代码看看浅拷贝的效果。
package mytest;
@Data//lombok注解
class Person implements Cloneable
{private int age;private String name;private Address address;public Person(int age, String name, Address address){this.age = age;this.name = name;this.address = address;}@Overrideprotected Object clone() throws CloneNotSupportedException{return super.clone();}public String display(){return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";}
}@Data//lombok注解
class Address
{private String province;private String street;public Address(String province, String street){this.province = province;this.street = street;}@Overridepublic String toString(){return "Address [province=" + province + ", street=" + street + "]";}
}public class Client
{public static void main(String[] args) throws CloneNotSupportedException{Person person = new Person(15, "sowhat", new Address("河北", "建华南大街"));Person clonePerson = (Person) person.clone();System.out.println(person);System.out.println(clonePerson);// 信息完全一样System.out.println(person.display());System.out.println(clonePerson.display());System.out.println("信息完全一致");System.out.println("原始年龄:" + person.getAge());System.out.println("克隆后原始年龄:" + clonePerson.getAge());System.out.println("年龄完全一样");System.out.println("原始名字哈希值:" + person.getName().hashCode());System.out.println("克隆后名字哈希值:" + clonePerson.getName().hashCode());System.out.println("字符串哈希值完全一样");clonePerson.setName("xiaomai");clonePerson.setAge(20);clonePerson.getAddress().setStreet("中山路");System.out.println(clonePerson.display());System.out.println(person.display());System.out.println("年龄跟姓名 是完全的深拷贝 副本跟原值无关的!");System.out.println("地址信息的修改是浅拷贝 ");}
}
结果如下:
mytest.Person@15f550a
mytest.Person@6b2d4a
Person [age=15, name=sowhat, address=Address [province=河北, street=建华南大街]]
Person [age=15, name=sowhat, address=Address [province=河北, street=建华南大街]]
信息完全一致
原始年龄:15
克隆后原始年龄:15
年龄完全一样
原始名字哈希值:-1432601412
克隆后名字哈希值:-1432601412
字符串哈希值完全一样
Person [age=20, name=xiaomai, address=Address [province=河北, street=中山路]]
Person [age=15, name=sowhat, address=Address [province=河北, street=中山路]]
结论:
原对象与新对象是两个不同的对象。
拷贝出来的新对象与原对象内容一致
接着将新对象里面的信息进行了修改,然后输出发现原对象里面的部分信息也跟着变了。其中基本类型跟 String类型的改变不会影响到原始对象的改变。而其他的Ojbect 类型改变的时候会影响到原始数据。上面的结论称为浅拷贝。即创建一个新对象,然后将当前对象的非静态字段复制到该对象,如果字段类型是值类型(基本类型跟String)的,那么对该字段进行复制;如果字段是引用类型的,则只复制该字段的引用而不复制引用指向的对象(也就是只复制对象的地址)。此时新对象里面的引用类型字段相当于是原始对象里面引用类型字段的一个副本,原始对象与新对象里面的引用字段指向的是同一个对象。因此,修改clonePerson里面的address内容时,原person里面的address内容会跟着改变。
深拷贝
了解了浅拷贝,那么深拷贝是什么也就很清楚了。那么该如何实现深拷贝呢?Object 类提供的 clone 是只能实现浅拷贝的,即将引用类型的属性内容也拷贝一份新的。
那么,实现深拷贝我这里收集到两种方式:第一种是给需要拷贝的引用类型也实现Cloneable接口并覆写clone方法;第二种则是利用序列化。
接下来分别对两种方式进行演示:
深拷贝-clone方式
对于以上演示代码,利用clone方式进行深拷贝无非就是将Address类也实现Cloneable,然后对Person的clone方法进行调整。让每个引用类型属性内部都重写clone() 方法,既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Person 类有一个引用类型 Address(其实String 也是引用类型,但是String类型有点特殊,后面会详细讲解),我们在 Address 类内部也重写 clone 方法。如下:
package mytest;
@Data//lombok注解
class Person implements Cloneable
{private int age;private String name;private Address address;protected int abc = 12;public Person(int age, String name, Address address){this.age = age;this.name = name;this.address = address;}@Override // clone 重载protected Object clone() throws CloneNotSupportedException{Person person = (Person) super.clone();//手动对address属性进行clone,并赋值给新的person对象person.address = (Address) address.clone();return person;}public String display(){return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";}
}@Data//lombok注解
class Address implements Cloneable
{private String province;private String street;public Address(String province, String street){this.province = province;this.street = street;}// 深拷贝时添加@Overrideprotected Object clone() throws CloneNotSupportedException{return super.clone();}@Overridepublic String toString(){return "Address [province=" + province + ", street=" + street + "]";}
}public class Client
{public static void main(String[] args) throws CloneNotSupportedException{Person person = new Person(15, "sowhat", new Address("河北", "建华南大街"));Person p1 = person;p1.setAge(45);System.out.println(p1.hashCode());System.out.println(person.hashCode());System.out.println(p1.display());System.out.println(person.display());System.out.println("-----------");Person clonePerson = (Person) person.clone();System.out.println(person);System.out.println(clonePerson);// 信息完全一样System.out.println(person.display());System.out.println(clonePerson.display());System.out.println("信息完全一致");System.out.println("原始年龄:" + person.getAge());System.out.println("克隆后原始年龄:" + clonePerson.getAge());System.out.println("年龄完全一样");System.out.println("原始名字哈希值:" + person.getName().hashCode());System.out.println("克隆后名字哈希值:" + clonePerson.getName().hashCode());System.out.println("字符串哈希值完全一样");clonePerson.setName("sowhat1412");clonePerson.setAge(20);clonePerson.getAddress().setStreet("中山路");System.out.println(clonePerson.display());System.out.println(person.display());System.out.println("年龄跟姓名 是完全的深拷贝 副本跟原值无关的!");System.out.println("地址信息的修改是浅拷贝 ");}
}
但是这种做法有个弊端,这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法。但是如果 Address 类也存在一个引用类型,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
利用序列化
序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。
package mytest;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;/*** 利用序列化和反序列化进行对象的深拷贝* @author ljj*/
class DeepClone implements Serializable
{private static final long serialVersionUID = 1412L;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();}
}
@Data
class Person extends DeepClone
{private static final long serialVersionUID = 1L;private int age;private String name;private Address address;public Person(int age, String name, Address address)
{this.age = age;this.name = name;this.address = address;}public String display()
{return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";}
}@Data
class Address extends DeepClone
{private static final long serialVersionUID = 1412L;private String province;private String street;public Address(String province, String street)
{this.province = province;this.street = street;}@Overridepublic String toString()
{return "Address [province=" + province + ", street=" + street + "]";}public void setStreet(String street)
{this.street = street;}
}public class Client
{public static void main(String[] args) throws Exception
{Person person = new Person(15, "sowhat", new Address("河北", "建华南大街"));Person clonePerson = (Person) person.deepClone();System.out.println(person);System.out.println(clonePerson);System.out.println(person.display());System.out.println(clonePerson.display());clonePerson.setName("sowhat1412");clonePerson.setAge(20);Address address = clonePerson.getAddress();address.setStreet("中山路");System.out.println(clonePerson.display());System.out.println(person.display());}
}
更多精彩推荐☞GAN模型生成山水画,骗过半数观察者,普林斯顿大学本科生出品☞AWS新品直指微软,它会是改变数据库的“Game Changer”吗?☞常年“盘踞”数据库前五的 MongoDB,在中国有哪些新动向?☞开发者实测 M1 芯片报告:除了大型应用程序启动慢点,整体性能优秀☞APISIX 温铭:开源的本质是要拿开发者的杠杆|人物志☞Salesforce 为什么要收购 Slack?
点分享点点赞点在看
Java 拷贝,你能说出个 123 么?相关推荐
- java 跨服务器 文件拷贝,java拷贝远程服务器上文件
java拷贝远程服务器上文件 内容精选 换一换 已成功登录Java性能分析.待安装Guardian的服务器已开启sshd.待安装Guardian的服务器已安装JRE,JRE版本要求为Huawei JD ...
- java开发怎么打补丁_[Java教程]【NC】出补丁与打补丁
[Java教程][NC]出补丁与打补丁 0 2021-01-02 20:00:06 出补丁 什么是补丁? 如果我们的衣服上破了一个洞,可以拿块布给补上,这块布就是"补丁".程序也是 ...
- php的web表单系统源码毕设_从业十多年看了千百套Java毕设项目,整理出100个精品!免费分享...
加班无数个昼夜看了千百套Java毕设项目,发现这100个精品!今天免费分享给大家!再给大家推荐一条由浅入深的JAVA学习路径,首先完成 Java基础.JDK.JDBC.正则表达式等基础实验,然后进阶到 ...
- java取负数_[Java] 告别“CV 工程师”码出高效!(基础篇)
作为一名资深的 CV 工程师,某天,当我再一次日常看见满屏的报错信息与键盘上已经磨的泛白的 Ctrl.C.V 这三个按键时,我顿悟了. 百度谷歌复制粘贴虽然很香,但是总是依靠前人种树,终会有一天失去乘 ...
- java鼠标右击出现选择窗口_java菜单代码 java中鼠标右击弹出菜单怎样实现
帮忙给一个java菜单栏例子的源代码 给你个小例子,已经添加注释了.自己运行下看看效果,满意的话记得结贴子. import java.awt.BorderLayout; import java.awt ...
- java拷贝远程服务器上文件,java拷贝远程服务器上文件
java拷贝远程服务器上文件 内容精选 换一换 在Windows模式下,调试功能暂不可用.为支持多交叉架构的调试场景,需要在安装MindStudio的服务器(UI Host)上安装gdb-multia ...
- 什么是真正的架构设计?某厂十年Java经验让我总结出了这些,不愧是我
本文转载自:什么是真正的架构设计?某厂十年Java经验让我总结出了这些,不愧是我 一. 什么是架构和架构本质 在软件行业,对于什么是架构,都有很多的争论,每个人都有自己的理解. 此君说的架构和彼君理解 ...
- Java接口入参和出参规范建议
把最近项目中遇到的问题跟大家分享下:最近做了一个项目,后端接口写好后和pc端vue联调完毕,业务还需要和App端[ios,andro]对接,问题来了,后端接口中的入参和出参都是Java数据类型[Dat ...
- java 除以1000_练习:将从表读出来的时间戳除以1000(java读时间戳会多出3个000)用jackson包 实现...
练习:将从表读出来的时间戳除以1000(java读时间戳会多出3个000)jackson包 实现 entity @Entity @DynamicUpdate //自动更新日期 @Data //get/ ...
- select Java Aplication ,eclipse弹出窗口
select Java Aplication 运行java web项目,弹出窗口,然后,确定之后,java虚拟机报错,java异常. 事情是这样的,今天我在练习spring的一个小例子.选择项目右键, ...
最新文章
- 使用Windows远程登录Ubuntu
- PCB产业对ERP软件提出了哪些挑战?
- Cilium 首次集成国内云服务,阿里云 ENI 被纳入新版本特性
- some screenshot for SAP Fiori smart template resource load
- MySQL函数/数据库函数
- 判断对象属性值是否为空
- Qt多线程应用--QRunnable
- Java学习笔记--反射API
- matlab三维三角网格,有限元分析利用matlab的gplot函数实现三维划分网格的方法
- Springboot项目启动报错:
- 病毒周报(081208至081214)
- c 易语言置入代码6,易语言置入代码 , 谁碰到这种情况_精易论坛
- 计算机u盘启动进不去怎么办,U盘启动盘怎么进不了PE系统 该如何解决
- SpringCloud整合LCN分布式事务模式
- BigDecimal中的大于等于、小于等于及加减乘除
- 编译和执行区别 c语言,C语言编译和执行分析
- buuctf-AWD-测试1
- 网络——介质访问控制
- 移动web——微金所实战项目
- oracle字符集增加生僻字,Oracle SQL一个“生僻字”的优化
热门文章
- asp.net怎样在URL中使用中文、空格、特殊字符
- ZT pthread_detach
- 企业信息安全建设要点梳理
- H3C交换机配置ACL禁止vlan间互访
- full stack on the road
- golang——channel笔记
- JS 匿名函数 自执行
- [FFmpeg] 官方例子 demuxing_decoding.c
- php抓娃娃机器,vue制作抓娃娃机 - osc_icwhzig7的个人空间 - OSCHINA - 中文开源技术交流社区...
- 如何调位置_如何获得正确的驾驶坐姿?