序列化是将Java对象相关的类信息、属性、属性值等信息以一定的格式转换为字节流,反序列化时再将字节流表示的信息来构建出Java对象。过程中涉及到其它对象的引用对象也要参与序列化。
Java中实现序列化的方式有两种:1、实现Serializable接口。2、实现Externalizable接口。
1、实现Serializable接口
public class People implements Serializable { private String name; private int age; public People(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public static void main(String[] args) throws IOException, ClassNotFoundException { People people = new People("bobo", 26); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(people); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); People serialPeople = (People) objectInputStream.readObject(); System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge()); } }运行输出:name:bobo age:26
2、实现Externalizable接口
用Externalizable接口实现序列化时需要注意两点:
必须要提供公有的无参构造函数,否则会报InvalidClassException。必须要在writeExternal和readExternal中自己去实现序列化过程。 public class People implements Externalizable { private String name; private int age; public People() { } public People(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeInt(age); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); age = in.readInt(); } public static void main(String[] args) throws IOException, ClassNotFoundException { People people = new People("bobo", 26); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(people); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); People serialPeople = (People) objectInputStream.readObject(); System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge()); } }运行输出:name:bobo age:26
1、serialVersionUID的作用
在序列化操作时,经常会看到实现了Serializable接口的类会存在一个serialVersionUID属性,并且它是一个固定数值的静态变量。它主要用于验证版本的一致性。每个实现Serializable接口的类都拥有这么一个ID,在序列化的时候会一起被写入流中。在反序列化的时候就会被拿出来跟当前类的serialVersionUID值进行比较,两者相同则说明版本一致,可以反序列化成功,如果不通则反序列化失败。 两种serialVersionUID方式:
自己定义,比如比如:private static final long serialVersionUID = 1234567L。如果没定义,JDK会帮我们生成,生成规则是利用类名、类修饰符、接口名、字段、静态初始化信息、构造函数信息、方法名、方法修饰符、方法签名等组成的信息,经过SHA算法生成serialVersionUID 值。2、Transient 关键字作用
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到流中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
public class People implements Serializable { private transient String name; private transient int age; public People(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public static void main(String[] args) throws IOException, ClassNotFoundException { People people = new People("bobo", 26); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(people); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); People serialPeople = (People) objectInputStream.readObject(); System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge()); } }运行输出:name:null age:0
3、静态变量不会被序列化
这个也很好理解,我们序列化是针对对象的,而静态变量是属于类的。下面看一个例子:
public class People implements Serializable { private static int age = 10; public static void main(String[] args) throws IOException, ClassNotFoundException { People people = new People(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(people); age = 26;//改变静态变量的值 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); People serialPeople = (People) objectInputStream.readObject(); System.out.println(" age:" + age); } }运行输出:age:26,age的值改变了,证明age是没有被序列化的。
4、父类的序列化
如果一个子类实现了Serializable 接口而父类没有实现该接口,则在序列化子类时,子类的属性状态会被写入而父类的属性状态不会被写入。所以如果想要父类属性状态也一起参与序列化,就要让它也实现Serializable 接口。
如果父类未实现Serializable 接口则反序列化生成的对象会再次调用父类的构造函数,以此来完成对父类的初始化,所以父类的属性初始值一般都是类型的默认值。
public class Animal { protected int num; protected String color; public int getNum() { return num; } public String getColor() { return color; } } public class People extends Animal implements Serializable { private String name; private int age; public People(String name, int age, int num, String color) { this.name = name; this.age = age; this.num = num; this.color = color; } public String getName() { return name; } public int getAge() { return age; } public static void main(String[] args) throws IOException, ClassNotFoundException { People people = new People("bobo", 26,10,"red"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(people); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); People serialPeople = (People) objectInputStream.readObject(); System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge()); System.out.println("num:" + serialPeople.getNum() + " color:" + serialPeople.getColor()); } }运行输出:
name:bobo age:26 num:0 color:null5、被引用的类没有实现Serializable 接口则序列化不成功
序列化对象里面包含的任何引用类型的对象的类都要实现Serializable 接口,否则抛出java.io.NotSerializableException。
public class Brother { protected int num; protected String color; public Brother(int num, String color) { this.num = num; this.color = color; } public int getNum() { return num; } public String getColor() { return color; } } public class People implements Serializable { private String name; private int age; private Brother brother; public People(String name, int age, Brother brother) { this.name = name; this.age = age; this.brother = brother; } public String getName() { return name; } public int getAge() { return age; } public static void main(String[] args) throws IOException, ClassNotFoundException { Brother brother = new Brother(2, "red"); People people = new People("bobo", 26, brother); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(people); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); People serialPeople = (People) objectInputStream.readObject(); System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge() + " brother:" + brother); } }运行输出:
Exception in thread "main" java.io.NotSerializableException: Brother at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at People.main(People.java:23)6、自定义序列化过程
如果默认的序列化过程不能满足需求,我们也可以自定义整个序列化过程。这时候我们只需要在需要序列化的类中定义私有的writeObject方法和readObject方法即可。
public class People implements Serializable { private String name; private int age; private transient String color; public People(String name, int age, String color) { this.name = name; this.age = age; this.color = color; } public String getName() { return name; } public int getAge() { return age; } public String getColor() { return color; } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject();//默认序列化过程 color = (String) s.readObject(); } private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject();//默认序列化过程 s.writeObject(color); } public static void main(String[] args) throws IOException, ClassNotFoundException { People people = new People("bobo", 26, "red"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(people); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); People serialPeople = (People) objectInputStream.readObject(); System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge() + " color:" + serialPeople.getColor()); } }运行输出:
name:bobo age:26 color:red在color属性前加了transient 关键字,意思是不让color实现序列化,但是下面又自定义序列化过程在writeObject和readObject里面实现color的序列化,所以color属性是实现了序列化的。
7、为什么实现readObject()方法和writeObject()方法就可以自定义序列化过程?
readObject()和writeObject() 既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?原来,ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为private以至于供ObjectOutputStream来使用。
下面我们以ObjectInputStream来源码分析一下:
ObjectInputStream的readObject()方法—>调用到readObject0(boolean unshared)方法—>readOrdinaryObject(boolean unshared)方法—>readSerialData(Object obj, ObjectStreamClass desc)方法---->ObjectStreamClass类的invokeReadObject(Object obj, ObjectInputStream in)方法:
void invokeReadObject(Object obj, ObjectInputStream in) throws ClassNotFoundException, IOException, UnsupportedOperationException { if (readObjectMethod != null) { try { readObjectMethod.invoke(obj, new Object[]{ in }); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof ClassNotFoundException) { throw (ClassNotFoundException) th; } else if (th instanceof IOException) { throw (IOException) th; } else { throwMiscException(th); } } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }执行readObjectMethod.invoke(obj, new Object[]{ in }),通过反射的方式调用我们类中定义的readObject的私有方法。
