java序列化之writeObject 和readObject

什么是序列化和反序列化?

序列化:将对象转化为字节的过程称为序列化过程。
反序列化:将字节转化为对象的过程称为反序列化。

序列化主要应用于网络传输和数据存储的场景。在java中,只有类实现了java.io.serializable接口,该类才能被序列化。
示例Demo1.java:

package com.example.demo;

import java.io.*;

public class Demo1 {
    public static class Person implements Serializable {
        private static final long serialVersionUID = 233858934995755239L;
        private String firstName;
        private String lastName;

        public Person(String firstName,String lastName){
            System.out.println("Init Person...");
            this.firstName = firstName;
            this.lastName = lastName;
        }
        public String toString() {
            return String.format("Pearson.toString(): firstName=%s,lastName=%s", firstName, lastName);
        }
    }

    public static void main(String[] args) {
        String firstName="Li",LastName="Kelly";
        Person person  = new Person(firstName, LastName);
        System.out.println("序列化前:"+person.toString());
        ObjectOutputStream outStream;
        ObjectInputStream inStream;
        String filePath = "D:/demo/person.obj";
        try {
            //创建一个ObjectOutputStream输出流
            outStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化到文件filePath
            outStream.writeObject(person);

            inStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject = (Person)inStream.readObject();
            System.out.println("反序列化后:"+readObject.toString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

输出:

Init Person...
序列化前:Pearson.toString(): firstName=Li,lastName=Kelly

反序列化后:Pearson.toString(): firstName=Li,lastName=Kelly

可见对象反序列化,并不没有调用类的构造方法(反序列化没有输出Init Person...)。

注:

如果一个可序列化的类的成员不是基本类型,也不是String类型,那这个引用类型也必须是可序列化的,也即继承serializable,否则,会导致此类不能序列化。

比如上面的代码中,我们给Person类加一个Dog对象,这个Dog没有继承serializable。

package com.example.demo;

import java.io.*;

public class Demo1 {
    public static class Dog{

    }
    public static class Person implements Serializable {
        private static final long serialVersionUID = 233858934995755239L;
        private String firstName;
        private String lastName;
        private Dog dog;

        public Person(String firstName,String lastName, Dog dog){
            System.out.println("Init Person...");
            this.firstName = firstName;
            this.lastName = lastName;
            this.dog = dog;
        }
        public String toString() {
            return String.format("Pearson.toString(): firstName=%s,lastName=%s", firstName, lastName);
        }
    }

    public static void main(String[] args) {
        String firstName="Li",LastName="Kelly";
        Dog dog= new Dog();
        Person person  = new Person(firstName, LastName,dog);
        System.out.println("序列化前:"+person.toString());
        ObjectOutputStream outStream;
        ObjectInputStream inStream;
        String filePath = "D:/demo/person.obj";
        try {
            //创建一个ObjectOutputStream输出流
            outStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化到文件filePath
            outStream.writeObject(person);

            inStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject = (Person)inStream.readObject();
            System.out.println("反序列化后:"+readObject.toString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

此时执行代码会报java.io.NotSerializableException: com.example.demo.Demo1$Dog的错误。让Dog继承serializable则不会报错。

问:

①同一个对象序列化多次,会将这个对象序列化多次吗?否。java序列化同一对象,并不会将此对象序列化多次得到多个对象。

            outStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化到文件filePath
            outStream.writeObject(person);
            outStream.writeObject(person);

            inStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject1 = (Person)inStream.readObject();
            Person readObject2 = (Person)inStream.readObject();
            System.out.println(readObject1==readObject2);
            System.out.println("反序列化后:"+readObject1.toString());

output:

Init Person...
序列化前:Pearson.toString(): firstName=Li,lastName=Kelly
true
序列化后:Pearson.toString(): firstName=Li,lastName=Kelly

ReadObject1和ReadObject2是同一对象。

②有些时候,我们有这样的需求,某些属性不需要序列化。怎么做?使用transient关键字选择不需要序列化的字段。

示例:

package com.example.demo;

import java.io.*;

public class Demo1 {
    public static class Dog implements Serializable{

    }
    public static class Person implements Serializable {
        private static final long serialVersionUID = 233858934995755239L;
        private String firstName;
        private transient String lastName;
        private transient int age;
        private Dog dog;

        public Person(String firstName,String lastName, Dog dog, int age){
            System.out.println("Init Person...");
            this.firstName = firstName;
            this.lastName = lastName;
            this.dog = dog;
            this.age = age;
        }
        public String toString() {
            return String.format("Pearson.toString(): firstName=%s,lastName=%s,age=%s", firstName, lastName,age);
        }
    }

    public static void main(String[] args) {
        String firstName="Li",LastName="Kelly";
        Dog dog= new Dog();
        Person person  = new Person(firstName, LastName,dog,23);
        System.out.println("序列化前:"+person.toString());
        ObjectOutputStream outStream;
        ObjectInputStream inStream;
        String filePath = "D:/demo/person.obj";
        try {
            //创建一个ObjectOutputStream输出流
            outStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化到文件filePath
            outStream.writeObject(person);

            inStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject = (Person)inStream.readObject();
            System.out.println("反序列化后:"+readObject.toString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

输出:

Init Person...
序列化前:Pearson.toString(): firstName=Li,lastName=Kelly,age=23
反序列化后:Pearson.toString(): firstName=Li,lastName=null,age=0

从输出我们看到,使用transient修饰的属性(此处是lastName、age),java序列化时,会忽略掉此类字段。

反序列化出的对象,被transient修饰的属性是默认值。对于引用类型,值是null;基本类型,值是0;boolean类型,值是false。

③使用transient虽然简单,但是将此属性完全隔离在了序列化之外。有没有一种机制,可以定制对象序列化的方式?

答:有。通过重写writeObject与readObject方法,可以自己选择哪些属性需要序列化, 哪些属性不需要。如果writeObject使用某种规则序列化,则相应的readObject需要相反的规则反序列化,以便能正确反序列化出对象。

示例:

package com.example.demo;

import java.io.*;

public class Demo1 {
    public static class Dog implements Serializable{

    }
    public static class Person implements Serializable {
        private static final long serialVersionUID = 233858934995755239L;
        private String firstName;
        private transient String lastName;
        private transient int age;
        private Dog dog;

        public Person(String firstName,String lastName, Dog dog, int age){
            System.out.println("Init Person...");
            this.firstName = firstName;
            this.lastName = lastName;
            this.dog = dog;
            this.age = age;
        }
        public String toString() {
            return String.format("Pearson.toString(): firstName=%s,lastName=%s,age=%s", firstName, lastName,age);
        }
        private void writeObject(ObjectOutputStream out) throws IOException {
            //将名字反转写入二进制流
            out.writeObject(new StringBuffer(this.lastName).reverse());
            out.writeInt(age);
        }

        private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{
            //将读出的字符串反转恢复回来
            this.lastName = ((StringBuffer)ins.readObject()).reverse().toString();
            this.age = ins.readInt();
        }
    }

    public static void main(String[] args) {
        String firstName="Li",lastName="Kelly";
        Dog dog= new Dog();
        Person person  = new Person(firstName, lastName, dog,23);
        System.out.println("序列化前:"+person.toString());
        ObjectOutputStream outStream;
        ObjectInputStream inStream;
        String filePath = "D:/demo/person.obj";
        try {
            //创建一个ObjectOutputStream输出流
            outStream = new ObjectOutputStream(new FileOutputStream(filePath));
            //将对象序列化到文件filePath
            outStream.writeObject(person);

            inStream = new ObjectInputStream(new FileInputStream(filePath));
            Person readObject = (Person)inStream.readObject();
            System.out.println("反序列化后:"+readObject.toString());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

在上面的例子中,我们给Person类增加了writeObject和readObject方法。

private void writeObject(ObjectOutputStream out) throws IOException {
            //将名字反转写入二进制流
            out.writeObject(new StringBuffer(this.lastName).reverse());
            out.writeInt(age);
        }

        private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{
            //将读出的字符串反转恢复回来
            this.lastName = ((StringBuffer)ins.readObject()).reverse().toString();
            this.age = ins.readInt();
        }

输出:

Init Person...
序列化前:Pearson.toString(): firstName=Li,lastName=Kelly,age=23
反序列化后:Pearson.toString(): firstName=null,lastName=Kelly,age=23

在writeObject里,首先out.writeObject(new StringBuffer(this.lastName).reverse());这句的作用是将名字反转写入二进制流。我们打开D:/demo/person.obj文件,看下lastName的形式是不是反转的。

person.obj文件是乱码的,但是我们仍然可以看到我们想要的,Kelly变成了yllek。

writeObject第二句是out.writeInt(age);age属性我们加了transient关键字,理论上这个属性应该不会被序列化。然而我们在这里加上了这行代码,所以age又可以被序列化。

除了这个,我们还可以看到writeObject和readObject都是private方法,但是它却被外部类(ObjectOutputStream和ObjectInputStream)调用,并且它们既不存在与Java.lang.Object,也没有在Serializeable中声明,那它们是如何被ObjectOutputStream和ObjectInputStream调用的呢?
答:利用反射机制。ObjectOutputStream和ObjectInputStream使用了反射来寻找是否声明了这两个方法。因为它们使用getPrivateMethod,所以这些方法不得不被声明为priate以至于供ObjectOutputStream来使用。 

注:Write的顺序和read的顺序需要对应。譬如writeObject有多个字段都用writeInt写入流中,那么readint需要按照顺序将其赋值。

例:

private void writeObject(ObjectOutputStream out) throws IOException {
            //将名字反转写入二进制流
            out.writeObject(new StringBuffer(this.lastName).reverse());
            out.writeInt(age);
            out.writeObject(this.firstName);
        }

        private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{
            //将读出的字符串反转恢复回来
            this.lastName = ((StringBuffer)ins.readObject()).reverse().toString();
            this.firstName =(String) ins.readObject();
            this.age = ins.readInt();
        }

此时代码报错:

java.io.OptionalDataException
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1585)

现在主要在公众号PostgreSQL运维技术 更新,欢迎来踩~

悄悄放一张:

PostgreSQL运维技术 

 

  • 13
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值