对象克隆

对象克隆最简单的方式是:将对原对象的引用直接传给一个新的副本变量。这种方式存在很大的缺陷,两个变量中任何一个变量的改变都会影响另一个变量。

浅拷贝

利用Object类的clone方法,能够创建一个新的对象,并拷贝原对象的域 ,返回新对象的引用。

优点:使副本的操作与原变量的操作相对独立。

缺点:当浅克隆对象与原对象共享的子对象可变时,对可变子对象的操作仍会同时影响两个对象。

// 原对象,包含Date类子对象

Employee original = new Employee("John Public", 50000);

// 使用clone方法,两个对象共享对Date类子对象的引用

Employee copy = original.clone();

// 若修改Date类子对象,则两个对象中的Date类子对象的值都会改变

在上述情况中,由于Date类是可变的,导致浅克隆对象和原对象之间仍然存在影响。作为替代,应当使用不可变的LocalDate类。

Cloneable接口

实际上,Object类中的clone方法被声明为protected。因此,Object的子类只能调用clone来克隆它们自己的对象,而不能克隆其他类的对象。

注意:Cloneable接口是Java提供的标记接口。标记接口不包含任何方法,其唯一的作用是允许在类型查询中使用instanceof。

即使clone的默认实现(浅拷贝)能够满足要求,仍然需要实现Cloneable接口,并将clone重新定义为public,再调用super.clone()。

class Employee implements Cloneable {

// 如果调用clone的类没有实现Cloneable接口,则抛出CloneNotSupportedException异常

public Employee clone() throws CloneNotSupportedException {

// 调用超类的clone方法

return (Employee) super.clone();

}

}

深拷贝

当浅拷贝无法满足对象之间的独立性时,需要建立深拷贝,即克隆对象中可变的实例域。

class Employee implements Cloneable {

// 如果调用clone的类没有实现Cloneable接口,则抛出CloneNotSupportedException异常

public Employee clone() throws CloneNotSupportedException {

// 调用超类的clone方法

Employee cloned = super.clone();

// 对可变的hireDay子对象再次建立浅拷贝

cloned.hireDay = (Date) hireDay.clone();

return cloned;

}

}

序列化

对象序列化是Java语言支持的一种非常通用的机制,它可以将任何对象写出到输出流中,并在之后将其读回。使用对象序列化机制可能便捷地处理多态集合(例如,一个Employee记录数组包含Manager等子类实例)。

保存和加载序列化对象

使用ObjectOutputStream对象的writeObject方法保存数据对象,使用ObjectInputStream对象的readObject读回数据对象。使用序列化时必须实现Serializable接口,其和Cloneable接口很相似,没有任何方法,其他类不需要为实现它做出任何改动。

// 创建一个Employee对象和一个Manager对象

Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);

Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);

// 将两个对象存储在对象输出流中

ObjectOutputStream out = new PbjectOutputStream(new FileOutputStream("employee.dat"));

out.writeObject(harry);

out.writeObject(boss);

// 将两个对象从对象输入流中恢复

ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.dat"));

Employee e1 = (Employee) in.readObject();

Employee e2 = (Employee) in.readObject();

对象序列化工作机制如下:

每一个对象引用关联一个序列号。

第一次遇到每个对象时,保存其对象数据到输出流中。

如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为x的对象相同”。

读回时,过程相反:对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个序列号和新对象之间的关联。

当遇到“与之前保存过的序列号x的对象相同”标记时,获取与这个序列号相关联的对象引用。

注:序列化使用序列号代替了内存地址,允许将对象集合在机器之间进行传送。

对象序列化的文件格式

注:以下(x)格式均表示字节长度。

对象序列化文件开头:魔幻数字(2) + 版本号(2)

类描述符的存储格式:

72 + 类名长度(2) + 类名

指纹(8) + 标志(1) + 数据域描述符的数量(2) + 数据域描述符 + 78 + 超类类型(无超类为70)

其中,指纹指通过对类、超类、接口、域类型和方法签名按照规范方式排序,然后应用安全散列算法SHA得到的。SHA将数据块转换为20个字节的数据包,序列化机制只使用SHA的前8个字节。

标志字节由java.io.ObjectStreamConstants中定义的3位掩码构成:

static final byte SC_WRITE_METHOD = 1;

// class has a writeObject_method that writes additional data

static final byte SC_SERIALIZABLE = 2;

// class implements the Serializable interface

static final byte SC_EXTERNALIZABLE = 4;

// class implements the Externalizable interface

数据域描述符的格式如下:

类型编码(1) + 域名长度(2) + 域名 + 类名(若域是对象)

对象序列化的文件格式特点如下:

对象流输出中包含所有对象的类型和数据域。

每个对象都被赋予一个序列号。

相同对象的重复出现将被存储为对这个对象的序列号的引用。

修改默认的序列化机制

对于不可序列化的数据域,如只对本地方法有意义的存储文件句柄值等,序列化机制将其标记为transient。不可序列化的类的域也需要标记为transient。瞬时的域在对象序列化时被跳过。

在可序列化的类中可以定义writeObject方法和readObject方法:

private void writeObject(ObjectOutputStream out)

throws IOException;

private void readObject(ObjectInputStream in)

throws IOException, ClassNotFoundException;

定义上述方法的类在序列化时会调用这两个方法对其数据域进行序列化。

由于writeObject和readObject方法不关心超类数据和任何其他类的信息,若要对整个对象的存储和恢复负责,就要实现Externalizable接口,定义writeExternal方法和readExternal方法:

public void writeExternal(ObjectOutputStream out)

throws IOException;

public void readExternal(ObjectInputStream in)

throws IOException, ClassNoutFoundException;

注意:writeObject和readObject方法是私有的,且只能被序列化机制调用。而writeExternal和readExternal方法是共有的,且readExternal方法甚至潜在地允许修改现有对象的状态。

使用序列化技术克隆对象

只需将对象序列化到输出流中,然后将其读回,就能产生现有对象的深拷贝。在此过程中,可以使用ByteArrayOutputStream方法将数据保存到字节数组中。如下修改clone方法:

class SerialCloneable implements Cloneable, Serializable {

public Object clone() throws CloneNotSupportedException {

try {

ByteArrayOutputStream bout = new ByteArrayOutputStream();

try (ObjectOutputStream = new ObjectOutputStream(bout)) {

out.writeObject(this);

}

try (InputSteam bin = new ByteArrayInputStream(bout.toByteArray())) {

ObjectInputStream in = new ObjectInputStream(bin);

return in.readObject();

}

catch (IOException | ClassNotFoundException e) {

CloneNotSupportedException e2 = new CloneNotSupportException();

e2.initCause(e);

throw e2;

}

}

}

}

需要克隆的类只需继承SerialCloneable类即可。

java对象序列化克隆_Java8基础知识(三)对象克隆与序列化相关推荐

  1. 作为一名Java开发者应该掌握的基础知识汇总!

    Java语言作为热门编程语言之一,受到了更多的欢迎.今天小千就为大家介绍一下作为一名Java开发者应该掌握的基础知识. 一.修饰符 java语言中提供了一些修饰符,这些修饰符可以修饰类,变量和方法. ...

  2. Java开发者需要掌握的基础知识

    Java语言作为热门编程语言之一,受到了更多的欢迎.今天小千就为大家介绍一下作为一名Java开发者应该掌握的基础知识. 一.修饰符 java语言中提供了一些修饰符,这些修饰符可以修饰类,变量和方法.以 ...

  3. (六)JS基础知识三(走进作用域和闭包)【三座大山之二,不会闭包,基本不会通过】

    JS基础知识三(作用域和闭包) 提问 作用域 自由变量 闭包 this 提问 this的不同应用场景,如何取值 手写bind函数 实际开发中闭包的应用场景,举例说明 创建10个a标签,点击的时候弹出对 ...

  4. Java中易忽略的基础知识

    欢迎关注我的公众号[软件大爆炸] Java学习中的碎碎念 Java中易忽略的基础知识 Java面向对象基础 Java中的核心类 Java抽象类和接口 Java中的异常 Java中的泛型与集合 Java ...

  5. JAVA面试题之JVM基础知识

    JAVA面试题总结-JVM的基础知识 JAVA面试题之JVM基础知识 说一下JVM的主要组成部分及作用 说一下 jvm 运行时数据区? 说一下堆和栈的区别? 队列和栈是什么?有什么区别? 什么是双亲委 ...

  6. CSS基础知识---三种选择器

    CSS基础知识---三种选择器 选择器 标签选择器 id选择器 class选择器 全部代码 选择器 标签选择器 id选择器 class选择器 标签选择器 选择器优先级:id>class>标 ...

  7. Java核心技术 卷I:基础知识

    第一章 Java程序设计概述 太简单了,直接略过. 1.2 Java"白皮书"的关键术语 简单性:指相对于C++简单(指针.多重继承等),但设计者也并没有试图清楚C++中所有不适当 ...

  8. Java 面试知识点解析(一)——基础知识篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  9. 2022年Java应届生面试之基础知识题

    1.java有哪些数据类型 分为基本数据类型和引用数据类型. 基本数据类型:数值型(byte.short.int.long.float.double)字符型(char)布尔型(boolean). 引用 ...

最新文章

  1. C语言引用文件空格和换行,关于文件操作,碰到空格就换行
  2. 下单问题分析及解决方式
  3. Linux 桌面玩家指南:09. X Window 的奥秘
  4. VS2010中使用sprintf出现warning C4997: 'sprintf': This function or variable may be unsafe.
  5. (15)ZYNQ FPGA AXI-stream总线简介(学无止境)
  6. posix threads php,3分钟短文 | PHP多线程没用过,你可能错过了计算机最好的时代!...
  7. Kubernetes 小白学习笔记(14)--k8s集群路线-join原理
  8. linux看网络信息失败的原因,Linux版本登录提示网络错误
  9. listview 分页加载
  10. c51中的_crol_和_cror_
  11. 华为交换机 查ip冲突_怎么查看华为交换机已绑定的ip与mac
  12. php气泡效果,ps简单制作漂亮的人物气泡效果
  13. 2020年系统架构师案例分析
  14. 举个栗子~Minitab 技巧(6):使用 T 检验 分析产品质量
  15. [日常]mov文件转换为gif
  16. 软件设计模式—命令模式
  17. 处理一次 MYSQL 启动异常 Error writing file '/tmp/MYYEBa32' (Errcode: 28 - No space left on device)
  18. Classpath entry org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER will not be exported
  19. 《CSS揭秘》读后感
  20. 索尼将向日本PSP用户提供Skype网络电话服务

热门文章

  1. 使用nextInt()等接受输入时必须注意换行符的输入
  2. 数据可视化(二)Matplotlib pandas简易入门
  3. python中使用pickle进行序列化
  4. MySQL索引对NULL值的处理
  5. Spark DStream相关操作
  6. 机器学习算法Python实现:基于情感词典的文本情感分析
  7. 金九银十中,看看这31道Android面试题
  8. session图片验证码,页面和请求是两个地址。android手机好用,iphone 失效。
  9. 使用JMH做Java微基准测试
  10. 家居市场2015惨淡收官 智能家居迎风而起前景广阔