无锡网站制作高端,王也天葛优,手机网站备案密码找回的方法,企业商务网站建设策划书Java深拷贝与浅拷贝 一、深拷贝与浅拷贝二、实现方式1、使用Object中的clone方法2、通过对象序列化 (实现Serializable接口) 三、原型设计模式 一、深拷贝与浅拷贝
浅拷贝只会复制对象中基本数据类型数据和引用数据的对象内存地址#xff0c;不会递归的复制引用对象、以及引用… Java深拷贝与浅拷贝 一、深拷贝与浅拷贝二、实现方式1、使用Object中的clone方法2、通过对象序列化 (实现Serializable接口) 三、原型设计模式 一、深拷贝与浅拷贝
浅拷贝只会复制对象中基本数据类型数据和引用数据的对象内存地址不会递归的复制引用对象、以及引用对象的引用对象…即浅拷贝得到的对象跟原始对象共享数据。深拷贝不仅会复制索引还会复制数据本身得到的是一份完完全全的独立对象。所以深拷贝比浅拷贝来说更加厚实更加耗内存空间。
二、实现方式
1、使用Object中的clone方法
使用Object中的clone方法必须实现Cloneable接口才可以调用clone()方法否则抛出CloneNotSupportedException异常深拷贝也需要实现Cloneable接口同时其成员变量为引用类型的也需要实现Cloneable接口然后重写clone方法。Cloneable接口是一个空接口记作他是一个标记接口实现Cloneable接口的类被标记为可以被clone的类。
代码示例
import java.util.ArrayList;
import java.util.List;/*** Author Long* Date 2023/9/9 11:32*/
public class People implements Cloneable {private String name;private String age;private Info info; // 是一个对象也必须继续拷贝/*** 深拷贝** return Info*/public People deepClone() {People people;try {people (People) super.clone();people.info this.info.deepClone();} catch (CloneNotSupportedException exception) {people null;System.out.println(clone Info error: exception);}return people;}}class Info implements Cloneable {private String address;private ListString familyMember new ArrayList();/*** 深拷贝** return Info*/public Info deepClone() {Info info;try {info (Info) super.clone();info.familyMember new ArrayList();info.familyMember.addAll(this.familyMember);} catch (CloneNotSupportedException exception) {info null;System.out.println(clone Info error: exception);}return info;}
}如上述代码示例若People的属性不止三个这时如果想得到一份数据一样但是完全独立的对象则可以使用深拷贝的方式相当于复制对象。且People中存在Info引用对象则Info也得继续实现拷贝才是真正实现的深拷贝否则复制出来的Info是指向同一个内存对象即为浅拷贝。
2、通过对象序列化 (实现Serializable接口)
使用序列化的方式来复制对象 对象需要继承Serializable接口。先将对象序列化然后再反序列化成新的对象。 Serializable 接口是一个标记接口不用实现任何方法仅用于标识可序列化的语义。 反序列化实现 Serilaziable 接口的类并不会调用构造方法。反序列的对象是由 JVM(java虚拟机) 以存储的二进制位为基础来构造不通过构造方法生成。
Serialization(序列化)将 java 对象以一连串的字节序列保存在本地磁盘中的过程也可以说是保存 java 对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)避免程序运行结束后对象从内存中消失字节序列也方便在网络中传输。Deserialization(反序列化)将保存在磁盘文件中的 java 字节序列重新转换成 java 对象称为反序列化。
对象是序列化的作用:
1)对象持久化: 把对象的字节序列保存在磁盘或数据库中一般是存在文件里这使得对象的状态可以在程序重启或跨网络传输后得以恢复,方便数据的存储和传输。2)网络传输对象:在分布式系统中对象的序列化与反序列化可以实现对象在网络中的传输将对象转换成字节流后可以通过网络将字节流发送给其他节点,接收方再将字节流反序列化为对象进行处理。3)共享对象状态: 在多线程或分布式环境中对象的状态共享。可能引发并发访问问题,通过将对象序列化可以将对象的状态进行共享,从而实现对象状态的一致性和同步。
序列化步骤: 步骤一创建一个 ObjectOutputStream 输出流 步骤二调用 ObjectOutputStream 对象的 writeObject() 输出可序列化对象。
反序列化步骤: 步骤一创建一个 ObjectInputStream 输入流 步骤二调用 ObjectInputStream 对象的 readObject() 得到序列化的对象。
代码示例
import java.io.Serializable;
public class User implements Serializable {private String name;private Address2 address;public User(String name, Address2 address) {this.name name;this.address address;}public String getName() {return name;}public void setName(String name) {this.name name;}public Address2 getAddress() {return address;}public void setAddress(Address2 address) {this.address address;}/*** 深拷贝** return Object*/public User 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 (User) ois.readObject();}
}class Address2 implements Serializable {private String city;private String country;public Address2(String city, String country) {this.city city;this.country country;}public String getCity() {return city;}public void setCity(String city) {this.city city;}public String getCountry() {return country;}public void setCountry(String country) {this.country country;}Overridepublic String toString() {return Address2{ city city \ , country country \ };}
}
class Test {public static void main(String[] args) {try {// 创建一个 ObjectOutputStream 输出流ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(D:\\person.txt));// 第一次序列化 person 对象Person person new Person(涛涛, 21);oos.writeObject(person);// 第二次序列化 personoos.writeObject(person);// 创建一个 ObjectInputStream 输入流ObjectInputStream ios new ObjectInputStream(new FileInputStream(D:\\person.txt));// 依次反序列化出 p1、p2Person p1 (Person) ios.readObject();Person p2 (Person) ios.readObject();// 判断 p1、p2 是否是同一对象int i1 System.identityHashCode(p1);int i2 System.identityHashCode(p2);System.out.println(i1 i1);System.out.println(i2 i2);} catch (Exception e) {e.printStackTrace();}}
} 输出结果
i1 1349393271
i2 1349393271
serializable 接口的注意事项:
如果实现 Serializable 接口的类有父类则父类也必须可以序列化若父类没有实现序列化接口则父类必须有无参构造函数否则会抛异常 java.io.InvalidClassException。因为在父类没有实现 Serializable 接口时虚拟机是不会序列化父对象的而一个 Java 对象的构造必须先有父对象才有子对象反序列化也不例外。所以反序列化时为了构造父对象只能调用父类的无参构造函数作为默认的父对象。序列化保存的是对象的状态静态变量属于类的状态因此序列化并不保存静态变量。即static属性不会被序列化。某些属性不需要序列化。使用 transient 关键字选择不需要序列化的字段。同一对象多次序列化并不会得到多个二进制流既不会反序列化为多个对象。而是只有第一次序列化为二进制流以后都只是保存序列化版本号且按自上而下的顺序依次保存。反序列化时的顺序与序列化时的顺序一致java 序列化提供了一个 “private static final long serialVersionUID” 的序列化版本号只要版本号相同即使更改了序列化属性对象也可以正确被反序列化回来。所以序列化一个类时最好指定一个序列化版本号方便项目升级。JVM 首先会通过类名来区分 Java 类类名不同则不是同一个类。当类名相同时JVM 就会通过序列化版本号来区分 Java 类如果序列化版本号相同就为同一个类序列化版本号不同就为不同的类。在序列化一个对象时如果没有指定序列化版本号后期对该类的源码进行修改并重新编译后可能会导致修改前后的序列化版本号不一致因为 JVM 会提供一个新的序列化版本号给该类对象此时再用以往的反序列化代码去反序列化该类的对象会导致反序列化使用的class的版本号与序列化时使用的不一致就会抛出异常 java.io.InvalidClassException。在Java中如果一个实现了Serializable接口的类没有指定serialVersionUID那么JVM在序列化时会根据属性自动生成一个serialVersionUID然后和属性一起序列化再进行网络传输或者持久化。在反序列化时JVM会再根据属性生成一个新版本的serialVersionUID然后再用这个新版本和serialVersionUID和序列化时生成的旧版本的serialVersionUID进行计较。如果二者一样就可以序列化成功。反之报错。如果我们显示指定了serialVersionUIDJVM在序列化和反序列化时就会使用我们指定的serialVersionUID。这样我们就可以确保在反序列化时serialVersionUID和之前的相同。
三、原型设计模式
深拷贝与浅拷贝就是原型设计模式的两种实现方式。
如果对象的创建成本比较大而同一个类的不同对象之间差别不大大部分字段都相同在这种情况下可以利用对已有对象原型进行复制拷贝的方式来创建新对象以达到节省创建时间的目的这种基于原型来创建对象的方式就叫做原形设计模式简称原形模式。
实际上创建对象包含的申请内存、给成员变量赋值这一过程本身并不会花费太多时间或者说对于大部分业务系统来说这些时间完全是可以忽略的应用一个复杂的模型只得到一点点的性能提升就是所谓的过渡设计、得不偿失。 但是如果对象中的数据需要经过复杂的计算才能得到比如排序、计算哈希值或者需要从PC网络数据库文件系统等非常慢速的io中读取这种情况下就可以使用原型模式从其他已有对象中直接拷贝得到而不用每次在创建新对象的时候都重复执行这些耗时的操作。