最近几天重新看了下序列化部分的内容,今天将它整理出来。
- 什么是序列化/反序列化
序列化是java开发中经常可以听到的一个词,那么究竟什么是序列化呢?简单来理解的话就是:将java对象转换成字节序列以二进制数据的形式保存在本地的操作就叫做序列化,将二进制数据还原成java对象的过程就叫反序列化。
我们画一个图来说明下:
2.怎么让对象序列化/反序列化
虽然序列化的目的是为了操作java对象,但并不是所有的java对象都可以进行序列化,想要实现序列化首需实现Serializable接口或者Externalizable接口,本文主要介绍Serializable接口。
我们先来看下java.io.Serializable接口:
可以看到这个接口仅仅是个标记,代表这个类可以序列化,没有任何方法,通过上面的@see注解可以知道它和对象处理流有关。
现在我们已经知道只要一个类实现了Serializable接口,就代表这个类可以序列化,那么通过什么操作可以将这个java对象以二进制数据的形式保存到本地呢?就是上面@see提到的对象处理流ObjectStream,我们写一个实例来了解一下整个过程:
可序列化的java对象:
package com.ljw.serialzable;
import java.io.Serializable;
/**
* @author liu.jiawei
* @create 2018-08-29 22:52
**/
public class Dog implements Serializable{
public String type;
public String name;
public int age;
public Dog(String type, String name, int age) {
this.type = type;
this.name = name;
this.age = age;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
序列化过程:
package com.ljw.serialzable;
import java.io.*;
/**
* @author liu.jiawei
* @create 2018-08-29 22:29
**/
public class TestSerializeDog {
public static void main(String[] args) {
File file = new File("Dog.txt");
try (FileOutputStream fis = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fis)) {
Dog dog = new Dog("藏獒", "旺财", 1);
oos.writeObject(dog);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
可以看到通过ObjectOutputStream这个对象处理流,可以实现java对象的序列化,处理流需要基于节点流发挥作用,ObjectOutputStream需要配合FileOutputStream一起使用。
看了序列化的过程,我们再将保存dog对象的文件重新还原成java对象:
package com.ljw.serialzable;
import java.io.*;
/**
* @author liu.jiawei
* @create 2018-08-29 23:01
**/
public class TestDeserializeDog {
public static void main(String[] args) {
File file = new File("/Users/liujiawei/ztesoft/JSYD/leetcode/dog.txt");
try (FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis)) {
Dog dog = (Dog) ois.readObject();
System.out.println(dog.getType() + "===" + dog.getName() + "===" + dog.getAge());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
可以看到,与序列化过程基本相似,只是ObjectOutputStream换成了ObjectInputStream,writeObject换成了readObject,类型强转程了需要的java 对象,这样就将之前的二进制数据还原成了对应的java对象。
还是上面的图来说明下过程:
3.序列化中的注意细节
- serialVersionUID
通过上面的两个例子,我们已经大概了解序列化和反序列化的过程,还有些细节需要我们注意,还是上面的dog对象,我们增加一个成员变量:
public int height;
再重新执行一下刚刚的反序列化的代码,是不是觉得很奇怪,这个两者有什么联系么?我们运行下代码:
是不是运行出错了,看它的报错提示我们可以知道有两个id不同导致了报错,这个id是干什么的呢?
这里面涉及到一个概念叫序列id(serialVersionUID),这个jvm在序列化操作文件时用来判断的一个变量,一般来说同一个class文件在不同情况编译的出来的serialVersionUID都是不同的,这就会导致了上面的那个情况,所以我们在声明一个java类是可序列化的时候,同时会定义一个静态常量serialVersionUID,idea可以对class生成对应的serialVersionUID,或者直接加上:
public static final long serialVersionUID = 1L;
*多个class可以用同一个serialVersionUID,并不影响判断。
我们在Dog类中加上serialVersionUID,重新执行反序列化过程,可以正常执行。
- transient
一个可序列化的java对象,有些时候,我们并不想暴露所有的成员变量,只需要在对应的变量前加上transient关键字,那么这个变量就不会被被序列化了,在反序列化的时候也就只能获取对应类型的默认值了。
我们将Dog类中的年龄字段隐藏:
public transient int age;
重新执行下反序列化过程,看下运行结果有没有不同:
可以看到,我们虽然反序列化了对象,但是并没有拿到隐藏的字段的值。
4.序列化思考
(1)子类没有实现序列化接口,基类实现,子类可以序列化么?
(2)子类实现序列化接口,基类没有实现,子类可以序列化么?
(3)内部类实现序列化接口,外部类没有实现,内部类可以序列化么?
废话不多说,直接上代码来一个个测试下:
(1):
子类:
package com.ljw.serialzable;
/**
* @author liu.jiawei
* @create 2018-08-29 23:35
**/
public class Man extends Person{
public String name;
public Man(String sex) {
super(sex);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
基类:
package com.ljw.serialzable;
import java.io.Serializable;
/**
* @author liu.jiawei
* @create 2018-08-29 23:34
**/
public class Person implements Serializable {
public String sex;
public Person(String sex) {
this.sex = sex;
}
}
序列化:
package com.ljw.serialzable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* @author liu.jiawei
* @create 2018-08-29 23:36
**/
public class TestSerializeMan {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("man.txt"))) {
oos.writeObject(new Man("man"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
结论:可以
(2)
子类:
package com.ljw.serialzable;
import java.io.Serializable;
/**
* @author liu.jiawei
* @create 2018-08-29 23:39
**/
public class Cat extends Animal implements Serializable{
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat(String type) {
super(type);
}
public Cat(String type, String name) {
super(type);
this.name = name;
}
}
基类:
package com.ljw.serialzable;
/**
* @author liu.jiawei
* @create 2018-08-29 23:39
**/
public class Animal {
public String type;
public Animal(String type) {
this.type = type;
}
}
序列化:
package com.ljw.serialzable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* @author liu.jiawei
* @create 2018-08-29 23:40
**/
public class TestSerializeCat {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cat.txt"))) {
oos.writeObject(new Cat("波斯猫"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
结论:可以
(3)
包含内部类的外部类:
package com.ljw.serialzable;
import com.ljw.ColleactionAndMap.T;
import java.io.*;
/**
* @author liu.jiawei
* @create 2018-08-29 23:43
**/
public class TestInnerClass {
class Dog implements Serializable{
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dog(String name) {
this.name = name;
}
}
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ddd.txt"))) {
TestInnerClass testInnerClass = new TestInnerClass();
TestInnerClass.Dog dog = testInnerClass.new Dog("旺财");
oos.writeObject(dog);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
结论:不可以,外部类也必须实现序列化接口。