欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

吃透Java基础九:序列化

程序员文章站 2022-05-14 17:16:02
...

一:什么是序列化

序列化是将Java对象相关的类信息、属性、属性值等信息以一定的格式转换为字节流,反序列化时再将字节流表示的信息来构建出Java对象。过程中涉及到其它对象的引用对象也要参与序列化。

二:序列化的应用场景

  1. 永久性保存对象,保存对象的字节序列到本地文件或者数据库中。
  2. 通过序列化以字节流的形式使对象在网络中进行传递和接收。
  3. 通过序列化在进程间传递对象。

三:序列化的实现方式

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接口实现序列化时需要注意两点:

  1. 必须要提供公有的无参构造函数,否则会报InvalidClassException。
  2. 必须要在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方式:

  1. 自己定义,比如比如:private static final long serialVersionUID = 1234567L。
  2. 如果没定义,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的私有方法。