Java序列化之Serializable
Java的序列化流程如下:
Java的反序列化流程如下:
注意:并不是所有类都需要进行序列化,主要原因有两个
1)安全问题。Java中有的类属于敏感类,此类的对象数据不便对外公开,而序列化的对象数据很容易进行破解,无法保证其数据的安全性,因此一般这种类型的对象不会进行序列化。
2)资源问题。可以使用序列化字节流创建对象,而且这种创建时不受限制的,有时过多地创建对象会造成很大的资源问题,因此此类对象也不适宜进行序列化。
Serializable
Serializable是Java提供的一个序列化接口,它是一个空接口,专门为对象提供标准的序列化跟反序列化操作。
序列化过程:
Person p = new Person("name","id"); File file = new File("cache.txt"); FileOutputStream output = new FileOutputStream(file); ObjectOutputStream objectOutputStream = new ObjectOutputStream(output); objectOutputStream.writeObject(p); output.close(); objectOutputStream.close();
反序列化过程:
File file = new File("cache.txt"); FileInputStream input= new FileInputStream(file); ObjectInputStream objectInputStream = new ObjectInputStream(input); Person p = (Person)objectInputStream.readObject(); System.out.println(p.getName()+"---"+p.getId()); input.close(); objectInputStream.close();
需要序列化的类成员
对象序列化时并不是所有成员都要转换成二进制的字节序列,因为为了节省存储或传输空间以及提高序列化效率,有些不必要的成员是无需序列化的。其中包括:
静态变量。因为静态变量属于类的属性,并不属于某个具体实例,因此在序列化的时候无须进行序列化,反序列化时,可以直接获取类的静态成员引用。
方法。方法只是一系列的操作集合,方法不会依赖对象,不会因为对象的不同,而操作不同,反序列化时,也可以从类中直接获取方法信息。
继承关系的序列化
父类实现Serializable时,子类被序列化,父类也会被序列化。
父类没有实现Serializable时,子类被序列化,父类不会被序列化
引用关系的序列化
如果对一个实现了Serializable的类进行序列化操作,则同时对它的引用类进行序列化操作。如果引用类没有实现Serializable接口,JVM会抛出java.io.NotSerializableExeception.
class Person implements Serializable{ private String name; private Tool tool = new Tool(); } class Tool implements Serializable{ }
此时对Person类进行序列化操作,则会同时对Tool类进行序列化操作。若Tool类没有实现Serializable接口,则会抛出异常。
保护敏感数据:
一个类加上序列化标识后,该类对象的所有属性信息将被序列化,然后进行本地存储或网络传输。然后有时对象中的某些字段属于敏感信息,不应暴露出来。如果对其也进行序列化,容易被破解,从而 造成安全隐患,例如常见的密码字段。
Java提供一个关键字transient,即瞬时关键字。该关键字关闭字段的序列化,这样受保护的信息就不会因为序列化而对外暴露。
序列化标识ID
试想一下这样的情景:两端进行网络传输序列化对象,由于某种原因,导致两端使用的类的版本不同,假设接收方的类被删除了几个字段。当发送发将对象的序列化字节流发送到接收方时,由于接收方 的类少了几个字段,而无法解析。
Java要求实现序列化接口的类都必须声明一个serialVersionUID静态属性,如果没有该属性JVM也会自动声明该属性,并为该属性赋值(当类发生改变时会赋予不同的值)。该属性的值是唯一的,用于 标识不同的序列化类。只有类的序列化标识完全相同,Java才会进行反序列化工作,这就是序列化标识的作用。
对于前面提到的情景,假设没有手动声明serialVersionUID,则JVM对发送方跟接收方使用的类中的serialVersionUID赋予不同的值,则反序列化失败。当手动给serialVersionUID赋值时,即使类的字 段发生改变,也能够反序列化成功。
自定义序列化策略
定制序列化策略
Java提供了一套有效的机制,允许在序列化和反序列化时,使用定制的方法进行相应的处理。当传输双方协定好序列化策略后,只需要在需要传输的序列化类中添加一组方法来实现这组策略,在序列化时会自动调用这些规定好的方法进行序列化和反序列化。方法如下:
1)private void writeObject(ObjectOutputSteam out) throws IOException
在方法的内部有重要的代码:out.defaultWriteObject() //将对象数据以默认方式写入到输出流中
2)private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException
同样的,此方法内部也有相似代码:in.defaultReadObject(); //以默认方式从输入流中恢复对象
这两个方法的作用分别是将特定的对象写入到输出流中以及从输入流中恢复特定的对象,通过这两个方法,用户即可实现自定义的序列化。当在实现Serializable接口的类中写了上面两个方法之后,序列化或反序列化该类时则会通过反射来调用这两个方法,从而实现自定义序列化。
限制序列化对象的数量
我们看下面的单例模式:
public class Singleton implements Serializable { private volatile static Singleton mInstance; private Singleton() { } public static Singleton getInstance() { if (mInstance == null) { synchronized (Singleton.class) { if (mInstance == null) { mInstance = new Singleton(); } } } return mInstance; } }
此时通过反序列化获取实例,则单例模式会失效。那该如何解决这个问题呢?
Java有一种机制,可以让我们在序列化和反序列化时,可以根据自己的需要,写入或读取指定的实例。使用这种机制,需要在实现Serializable接口的类中添加两个方法:
private Object readResolve() //如果用户在序列化类中添加了该方法,则在进行反序列化时,使用该方法返回的对象,作为反序列化对象。
private Object writeReplace() //如果用户在序列化类中添加了该方法,则在进行序列化时,序列化该类返回的对象。
再看使用了该机制的单例模式:
public class Singleton implements Serializable { private volatile static Singleton mInstance; private Singleton() { } public static Singleton getInstance() { if (mInstance == null) { synchronized (Singleton.class) { if (mInstance == null) { mInstance = new Singleton(); } } } return mInstance; } private Object readResolve() { return getInstance(); } private Object writeReplace() { return getInstance(); } }
此时的通过反序列化得到的对象也是同一个,即单例模式依然有效!
相关文章:
Java序列化Serializable和Externalizable区别的示例代码

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

胶囊是一种三维几何图形,由一个圆柱体和两端各一个半球体组成。胶囊的体积可以通过将圆柱体的体积和两端半球体的体积相加来计算。本教程将讨论如何使用不同的方法在Java中计算给定胶囊的体积。 胶囊体积公式 胶囊体积的公式如下: 胶囊体积 = 圆柱体体积 两个半球体体积 其中, r: 半球体的半径。 h: 圆柱体的高度(不包括半球体)。 例子 1 输入 半径 = 5 单位 高度 = 10 单位 输出 体积 = 1570.8 立方单位 解释 使用公式计算体积: 体积 = π × r2 × h (4

Java是热门编程语言,适合初学者和经验丰富的开发者学习。本教程从基础概念出发,逐步深入讲解高级主题。安装Java开发工具包后,可通过创建简单的“Hello,World!”程序实践编程。理解代码后,使用命令提示符编译并运行程序,控制台上将输出“Hello,World!”。学习Java开启了编程之旅,随着掌握程度加深,可创建更复杂的应用程序。
