Java中序列化和反序列化以及关键字 transient
总结
序列化的场景和作用:内存对象状态持久化、网络传输、远程方法调用传输对象
实现序列化方式:Serialization序列化接口、Externalizable外部序列化接口实现writeExtenal()和readExtenal()
序列化:对象输出流ObjectOutputStream和writeObject()
反序列化:对象输入流ObjectInputStream和readObject()
serialVersionUID:序列化版本标识的 静态final变量
Transient:阻止序列化
静态变量:不能被序列化
参考:
Java中的关键字 transient
java序列化与反序列化总结
详细说明
1、序列化和反序列化的概念
把Java对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为Java对象的过程称为对象的反序列化。
2、什么情况下需要序列化
当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
当你想用套接字在网络上传送对象的时候;
当你想通过RMI(远程方法调用)传输对象的时候;
3、如何实现序列化呢、使用场景?
Java提供两种对象持久化的方式,分别序列化和外部序列化。
序列化:实现Serialization接口(位于java.lang包中);
外部序列化:实现了Externalizable接口,Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为;
外部序列化与序列化的区别:
序列化是内置的API,只需要,只需要实现Serialization;
使用外部序列化时,接口必须由开发人员来实现,由于控制权交给了开发人员,在编程时有更多的灵活性,可能会提高性能。Externalizable接口提供了两个方法writeExternal()和readExternal()
对Serializable对象反序列化时,并不会调用任何构造函数 ,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。
对Externalizable对象反序列化时,会先调用类的不带参数的构造方法 ,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
4、对象序列化和反序列化的步驟?
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
public class 序列化测试
{
public static void main(String[] args) throws Exception
{
//序列化对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("d:/objectFile.data"));
MyObject obj1 = new MyObject("liuhongliang", 24);
out.writeObject("hello world!");
out.writeObject(new Date());
out.writeObject(obj1);
out.writeInt(54);
out.close();
//反序列化对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream(("d:/objectFile.data")));
System.out.println("obj1=" + (String) in.readObject());
System.out.println("obj2=" + (Date) in.readObject());
MyObject obj3 = (MyObject) in.readObject();
System.out.println("obj3=" + obj3);
int obj4 = in.readInt();
System.out.println("obj4=" + obj4);
in.close();
}
}
//自定义可序列化类
class MyObject implements Serializable
{
private String name;
private int age;
public MyObject(String name, int age)
{
this.name = name;
this.age = age;
}
public String toString()
{
return "name=" + name + ", age=" + age;
}
}
5、serialVersionUID是什么?
总结:序列化和反序列化的版本控制
凡是实现Serializable接口的类都应当有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;
当我们的类实现了Serializable接口后,会有一个警告,告诉你需要生成一个serialVersionUID属性。这个serialVersionUID是做什么用的呢?其实这是JAVA序列化的版本控制功能。当序列化对象时会把这个属性写入,当反序列化时则会把这个属性取出,然后与JAVA类中的serialVersionUID属性值对比,如果一致,则认为是同一个版本,正常反序列化,如果不一致则认为版本不同,抛出InvalidClassException异常。
6、Transient关键字
作用:就是让某些被修饰的成员属性变量不被序列化;
用途:一些敏感信息,比如银行密码账号之类的,不希望网络传输或者磁盘持久化,使用transient关键字,这个字段的生命周期仅存与调用者的内存而不会写入磁盘里持久化
注意:
Transient修饰的变量和静态变量都不能被序列化;
一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问;
在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
例如:HashMap中的modCount变量、StringBuffer用于存储的字符数组char[] toStringCache
7、Serializable接口中有几个方法?如果没有方法的话,那么这么设计Serializable接口的目的是什么?
Serializable接口在java.lang包中,是Java序列化机制的核心组成部分。它里面没有包含任何方法,我们称这样的接口为标识接口。如果你的类实现了Serializable接口,这意味着你的类被打上了“可以进行序列化”的标签,并且也给了编译器指示,可以使用序列化机制对这个对象进行序列化处理。
8、在Java进行序列化和反序列化处理的时候,哪些方法被使用了?
这个是面试中常见的问题,主要用来考察你是否对readObject()、writeObject()、readExternal()和writeExternal()方法的使用熟悉。
Java序列化是通过java.io.ObjectOutputStream这个类来完成的。这个类是一个过滤器流,这个类完成对底层字节流的包装来进行序列化处理。
通过ObjectOutputStream.writeObject(obj)进行序列化;
通过ObjectInputStream.readObject()进行反序列化。
对writeObject()方法的调用会触发Java中的序列化机制。readObject()方法用来将已经持久化的字节数据反向创建Java对象,该方法返回Object类型,需要强制转换成你需要的正确类型。
9、假设一个新的类的超类实现了Serializable接口,那么如何让这个新的子类不被序列化?
重新实现writeObject()和readObject()方法,并在方法实现中通过抛出NotSerializableException