Java序列化详解
1.简介
Java的序列化是指将对象编码成一个字节流,这个字节流可以在网络中传输,同时序列化也是将Java对象持久化的一种方式。
Java的序列化机制是在运行时判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把字节流中的serialVersionUID
和本地对应实体的serialVersionID进行比较,如果相同就认为是一致的。否则就会出现序列化版本不一致的异常。
2.方式
通过java.io.ObjectOutputStream
类的writeObject()
方法可以实现序列化;
通过java.io.ObjectInputStream
类的readObject()方法
实现反序列化。
public class SerialTest {
public static void serialize(Object o, String path) throws IOException {
File file = new File(path);
OutputStream fileInputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
objectOutputStream.writeObject(o);
objectOutputStream.close();
fileInputStream.close();
}
public static Object reSerialize(String path) throws IOException, ClassNotFoundException {
File file = new File(path);
InputStream inputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Object o = objectInputStream.readObject();
return o;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User("张三", 18, 1);
String path = SerialTest.class.getClassLoader().getResource("").getPath() + "user.txt";
System.out.println("path = " + path);
serialize(user, path);
User reUser = (User) reSerialize(path);
System.out.println("reUser = " + reUser); //reUser = User(name=张三, age=18, gender=1)
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
class User implements Serializable{
private String name;
private int age;
private int gender; //性别,0女,1男
}
3. Serializable 接口
被序列化的类必须实现Serializabel
或Externalizable
接口,否则将抛出 NotSerializableException
异常。因为在序列化的过程中会对要序列化的实例进行类型检查,如果不满足序列化要求就抛异常。
3.1SerialVersionUID
serialVersionUID
是 Java 为每个序列化类产生的版本标识。它可以用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的 serialVersionUID
与发送方发送的 serialVersionUID
不一致,会抛出 InvalidClassException
。
如果可序列化类没有显式声明 serialVersionUID
,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID
值。尽管这样,还是建议在每一个序列化的类中显式指定 serialVersionUID
的值。因为不同的 jdk 编译很可能会生成不同的 serialVersionUID
默认值,从而导致在反序列化时抛出 InvalidClassExceptions
异常。
serialVersionUID
字段必须是 static final long
类型
显式地定义serialVersionUID有两种用途:
(1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID
;在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID
。
(2)当你序列化了一个类实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID
,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。如果你添加了serialVersionUID
,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。
public class SerialTest2 {
public static void serialize(Object o, String path) throws IOException {
File file = new File(path);
OutputStream fileInputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
objectOutputStream.writeObject(o);
objectOutputStream.close();
fileInputStream.close();
}
public static Object reSerialize(String path) throws IOException, ClassNotFoundException {
File file = new File(path);
InputStream inputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Object o = objectInputStream.readObject();
return o;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
String path = SerialTest.class.getClassLoader().getResource("").getPath() + "user.txt";
// User2 user2 = new User2("张三", 18, 1);
// System.out.println("path = " + path);
// serialize(user2, path);
User2 reUser2 = (User2) reSerialize(path);
System.out.println("reUser = " + reUser2); //reUser = User2(name=张三, age=18, gender=1, email=null)
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
class User2 implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
private int gender; //性别,0女,1男
private String email;
}
将不含email
字段的user2
字段序列化后,给User2
类添加email
字段后反序列化,得到的反序列化对象是reUser = User2(name=张三, age=18, gender=1, email=null)
。如果没有显式指定serialVersionUID
则会报InvalidClassExceptions
异常。
3.2transient
在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
当某个字段被声明为 transient
后,默认序列化机制就会忽略该字段的内容,该字段的内容在序列化后无法获得访问。
class User implements Serializable{
private String name;
transient private int age;
private int gender; //性别,0女,1男, 2无
}
//output: reUser = User(name=张三, age=0, gender=1)
如果我们示例1中的age
字段使用transient
修饰后,反序列化的对象为reUser = User(name=张三, age=0, gender=1)
,可以看到age
字段没有被反序列化。
4.用途
对象的序列化主要有两种用途:
(1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
(2) 在网络上传送对象的字节序列。