Java 序列化是 JDK 1.1 时引入的一组开创性的特性,用于将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态。
序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。
在java中,实现Serializbale 接口的对象,都可以序列化、反序列化。Serializbale的定义:
public interface Serializable {
}
1、Serializbale就一个空的接口,竟然能够保证实现了它的“类的对象”被序列化和反序列化?
我们先看一个例子:
1)Person类:
public class Person {
private Long id;
private String name;
//set get 方法
}
2)序列化、反序列:
private static void test1() {
Person p = new Person();
p.setId(1L);
p.setName("test");
System.out.println(p);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://person"));){
oos.writeObject(p);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D://person")));){
Person p1 = (Person) ois.readObject();
System.out.println(p1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
由于Person类没有实现Serializbale接口,会报如下错误:
java.io.NotSerializableException: serialize.Person
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at serialize.Test.test1(Test.java:46)
at serialize.Test.main(Test.java:13)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: serialize.Person
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1575)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at serialize.Test.test1(Test.java:54)
at serialize.Test.main(Test.java:13)
Caused by: java.io.NotSerializableException: serialize.Person
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at serialize.Test.test1(Test.java:46)
... 1 more
顺着报错信息,我们来看一下 ObjectOutputStream 的 writeObject0() 方法。其部分源码如下:
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
也就是说,ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException。
如果Person类实现了Serializbale接口,那么就可以顺利序列化、反序列化操作了。如此可见:
Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。
常见的这类标识接口还有:
- java.lang.Cloneable:表明Object.clone()方法可以合法地对该类实例进行按字段复
- java.util.RandomAccess:用来表明其支持快速(通常是固定时间)随机访问
- java.rmi.Remote:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口
注:对于嵌套的类,外层类实现了Serializbale接口,内层类如果没有实现,在对外层类进行序列化时会报错。
2、关于serialVersionUID :
当一个类实现了Serializable 接口后,就会提醒该类最好产生一个序列化 ID,就像下面这样:
serialVersionUID 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常。
从上面提示可以看到,serialVersionUID 的生成有三种方式:
1)添加一个默认版本的序列化 ID:
private static final long serialVersionUID = 1L;
如果没有特殊需求,采用默认的序列化 ID(1L)就可以,这样可以确保代码一致时反序列化成功。
2)添加一个随机生成的不重复的序列化 ID:
private static final long serialVersionUID = -2095916884810199532L;
3)添加 @SuppressWarnings 注解:
@SuppressWarnings("serial")
使用 @SuppressWarnings("serial") 注解时,该注解会为被序列化类自动生成一个随机的序列化 ID。
3、static 和 transient 修饰的字段是不会被序列化的
我们看一个例子:
1)Person类:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
public static String preName;
private transient String lasName;
2)序列化、反序列化类:
private static void test1() {
Person p = new Person();
p.setId(1L);
p.setName("test");
p.setLasName("李斯");
Person.preName = "张三";
System.out.println(p);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://person"));){
oos.writeObject(p);
} catch (IOException e) {
e.printStackTrace();
}
Person.preName = "张三2";
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D://person")));){
Person p1 = (Person) ois.readObject();
System.out.println(p1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
输出:
Person [id=1, name=test,preName=张三, lasName=李斯]
Person [id=1, name=test,preName=张三2, lasName=null]
- static属性:因为序列化保存的是对象的状态,而static 修饰的字段属于类的状态,因此序列化并不保存 static 修饰的属性是合理的,反序列化后其属性值始是终保存在方法区中值;
- transient属性:它可以阻止属性被序列化到文件中,在被反序列化后,transient属性的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null。
深究源码,可以在 ObjectStreamClass 中发现下面这样的代码:
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT;
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
4、Externalizable接口:
除了 Serializable 之外,Java 还提供了一个序列化接口 Externalizable。实现Externalizable接口进行序列化、反序列化时要注意一下两点不同:
1)实现 Externalizable 接口必须重写writeExternal() 和 readExternal()方法:
public class Student implements Externalizable {
private Long id;
private String name;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(id);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
id = in.readLong();
name = (String)in.readObject();
}
2)被序列化的类必须要有默认构造方法:
使用 Externalizable 进行反序列化的时候,会调用被序列化类的无参构造方法去创建一个新的对象,然后再将被保存对象的字段值复制过去。如果类没有默认构造方法(无参),会抛出以下异常:
java.io.InvalidClassException: serialize.Student; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2041)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at serialize.Test.test3(Test.java:30)
at serialize.Test.main(Test.java:13)
参考: