吃透Java基础九:序列化
一:什么是序列化
序列化是将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:null
5、被引用的类没有实现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的私有方法。
上一篇: php源代码安装常见错误与解决办法分享_php技巧
下一篇: Java对象的序列化与反序列化
推荐阅读