type
status
date
slug
summary
tags
category
icon
password
Property
序列化是指把一个Java对象变成二进制内容,本质上就是一个
byte[]
数组。为什么要把Java对象序列化呢?因为序列化后可以把
byte[]
保存到文件中,或者把byte[]
通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。有序列化,就有反序列化,即把一个二进制内容(也就是
byte[]
数组)变回Java对象。有了反序列化,保存到文件中的byte[]
数组又可以“变回”Java对象,或者从网络上读取byte[]
并把它“变回”Java对象。一个Java对象要能序列化,必须实现一个特殊的
java.io.Serializable
接口,它的定义如下:Serializable
接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。把一个Java对象变为
byte[]
数组,需要使用ObjectOutputStream
。它负责把一个Java对象写入一个字节流:ObjectOutputStream
既可以写入基本类型,如int
,boolean
,也可以写入String
(以UTF-8编码),还可以写入实现了Serializable
接口的Object
。因为写入
Object
时需要大量的类型信息,所以写入的内容很大。反序列化
和
ObjectOutputStream
相反,ObjectInputStream
负责从一个字节流读取Java对象:除了能读取基本类型和
String
类型外,调用readObject()
可以直接返回一个Object
对象。要把它变成一个特定类型,必须强制转型。readObject()
可能抛出的异常有:ClassNotFoundException
:没有找到对应的Class;
InvalidClassException
:Class不匹配。
对于
ClassNotFoundException
,这种情况常见于一台电脑上的Java程序把一个Java对象,例如,Person
对象序列化以后,通过网络传给另一台电脑上的另一个Java程序,但是这台电脑的Java程序并没有定义Person
类,所以无法反序列化。对于
InvalidClassException
,这种情况常见于序列化的Person
对象定义了一个int
类型的age
字段,但是反序列化时,Person
类定义的age
字段被改成了long
类型,所以导致class不兼容。为了避免这种class定义变动导致的不兼容,Java的序列化允许class定义一个特殊的
serialVersionUID
静态变量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID
的值,这样就能自动阻止不匹配的class版本:要特别注意反序列化的几个重要特点:
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。
安全性
因为Java的序列化机制可以导致一个实例能直接从
byte[]
数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]
数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。