序列化&反序列化

对象序列化

1、对象序列化概述

序列化是一种对象的传输手段,Java有自己的序列化管理机制。对象序列化,就是把一个对象变为二进制的数据流的一种方法,通过对象的序列化可以方便地实现对象的传输或存储,如图 1-1所示。

图 1-1

一个类的实例化对象想被序列化,则对象所在的类必须实现java.io.Serializable接口。然而此接口并没有提供任何的抽象方法,所以该接口只是一个标识接口(只是表示一种对象可以被序列化的能力)

范例1:定义序列化对象类Student

//实现Serializable接口
public class Student implements Serializable {
    private String name;
    private Integer age;
    private Integer score;

    public Student() {
    }

    public Student(String name, Integer age, Integer score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

本程序定义的Student类实现了Serializable接口,所以此类的实例化对象都允许序列化转成二进制进行传输。

tips:对象序列化和对象反序列化操作时的版本兼容问题。

  • 在对象进行序列化或者反序列操作的时候,要考虑JDK版本的问题,如果序列化JDK版本和反序列化JDK版本不统一则有可能造成异常。所以在序列化操作中,引入了一个serialVersionUID的常量,可以通过此常量来验证版本的一致性,即serialVersionUID是序列化前后的唯一标识符。在进行反序列化时,JVM会把传来的字节流中的serialVerionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
  • 当实现java.io.Serializable接口的实体(类)没有显式地定义一个名为serialVersionUID,类型为long的变量时,Java序列化机制在编译时会根据该类的各方面信息自动产生一个默认的serialVersionUID,一旦更改了类的结构或者信息,则类的serialVersionUID也会跟着改变。
  • 为了serialVersionUID的确定性,写代码时还是建议凡是实现了java.io.Serializable接口的类,最好不要通过编译器来自动生成,人为显式地定义一个名为serialVersionUID,类型为long的变量,那么只要不修改这个变量值的序列化实体都可以相互进行序列化和反序列化。

2、序列化与反序列化处理

序列化和反序列化需要进行二进制存储格式上的统一,为此Java提供了对象序列化操作类。Serializable接口只是定义了某一个类的对象是否被允许序列化,然而对于序列化和反序列化操作的具体实现则需依靠ObjectOutputStreamObjectInputStream两类完成。这两类的继承结构如图 2-1所示。

图 2-1

ObjectOutputStream类可以将对象转为特定格式的二进制数据输出,常用方法如表 2-1所示;ObjectInputStream类可以读取ObjectOutputStream类输出的二进制对象数据,并将其转为具体类型的对象返回,常用方法如表 2-2所示。

表 2-1
方法类型描述
public ObjectOutputStream(ObjectOutputStream out) throws IOException构造传输输出的对象
public final void writeObject(Object obj) throws IOException普通输出对象
表 2-2
方法类型描述
public ObjectInputStream(InputStream in) throws IOException构造构造输入对象
public final Object readObject() throws IOException, ClassNotFoundException普通从指定位置读取对象

范例2:实现对象序列化和反序列化操作

/*
在Student类中添加两个方法
*/

//1、序列化操作
public static void serialize(Object object, String fileName) throws IOException {
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(
        new FileOutputStream(new File(fileName)));
    objectOutputStream.writeObject(object);
    objectOutputStream.close();

    System.out.println("序列化成功!");
}

//2、反序列化操作
public static Object deserialize(String fileName) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = new ObjectInputStream(
        new FileInputStream(new File(fileName)));
    Object object = objectInputStream.readObject();
    objectInputStream.close();

    System.out.println("反射序列化成功!");
    return object;
}
//测试类
public class SerializeTestDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student = new Student("Chen", 22, 22);
        System.out.println(student);
        //序列化
        Student.serialize(student, "chen.dat");
        System.out.println("==========");

        //反序列化
        Student chen = (Student) Student.deserialize("chen.dat");
        System.out.println(chen);
    }
}

/*
输出结果:
Student{name='Chen', age=22, score=22}
序列化成功!
==========
反射序列化成功!
Student{name='Chen', age=22, score=22}
*/

本程序以二进制文件(chen.dat)作为序列化操作的存储终端 ,在程序中首先通过ObjectOutputStream类将一个Student类的实例化对象输出到文件中,随后再利用ObjectInputStream类将二进制的序列化数据读取并转为Student类实例化对象返回。


3、transient 关键字

默认情况下,当执行对象序列化操作的时候,会将类中的全部属性的内容进行序列化操作,为了保证对象序列化的搞笑传输,就需要防止一些不必要的成员属性的序列化处理。当成员属性不需要进行序列化处理时就可以在属性定义上使用transient关键字来完成。

范例3:transient 关键字的序列化和反序列化操作

//将Student类的score字段定义为transient
private transient Integer score;

//测试类的代码不做任何改变

/*
输出结果:
Student{name='Chen', age=22, score=22}
序列化成功!
==========
反射序列化成功!
Student{name='Chen', age=22, score=null}
*/

本程序对Student类对象进行序列化处理,score属性的内容是不会被保存下来的,这样进行反序列化操作时,score使用的将是其数据类型的默认值。

tips:凡是被static修饰的字段是不会被序列化的。

因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域。

4、序列化的受控和加强

序列化和反序列化的过程其实是有漏洞的,因为从序列化到反序列化是有中间过程的,如果被人拿到中间字节流,然后加以伪造或者篡改,那么反序列化返回的实例化对象就会有一定的风险了。毕竟反序列化也是相当于一种**“隐式的”对象构造**,因此,我么希望在反序列时,进行受控的对象反序列化操作。

具体的操作就是自行编写readObject()方法,用于对象的反序列化构造,从而提高约束性。既然是自行编写readObject()方法,那就可以做更多的事情:比如说各种判断工作。

还是以上述的Student类说,一般而言,成绩的取值是在0~100之间,我们为了防止学生的成绩在反序列时被别人篡改,我们可以自行编写readObject()方法用于反序列的控制

/*
在Student类中再添加一个个方法
*/
//自行编写readObject()方法用于反序列的控制
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        //调用默认的反序列方法
        objectInputStream.defaultReadObject();

        //手工检测反序列化后学生成绩的有效性,若发现问题。立即终止操作!
        if (0 > score || 100 < score) {
            throw new IllegalArgumentException("分数只能在0~100之间");
        }
    }
//测试类
public class SerializeTestDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student01 = new Student("cbc", 222, 22);
        System.out.println(student01);
        //序列化
        Student.serialize(student01, "cbc.dat");
        //反序列化
        Student cbc = (Student) Student.deserialize("cbc.dat");
        System.out.println(cbc);

        System.out.println("==============================");
        Student student02 = new Student("zhang", 2, 101);
        System.out.println(student02);
        //序列化
        Student.serialize(student02, "zhang.dat");
        //反序列化
        Student zhang = (Student) Student.deserialize("zhang.dat");
        System.out.println(zhang);

    }
}

/*
输出结果:
序列化成功!
反射序列化成功!
Student{name='cbc', age=222, score=22}
==============================
Student{name='zhang', age=2, score=101}
序列化成功!
Exception in thread "main" java.lang.IllegalArgumentException: 分数只能在0~100之间
*/    

对于上面的代码,我一开始也是很懵,怎么自定义为private的readObject()方法也可以被自行调用。后来看到大牛的博客后,看到ObjectStreamClass类最底层的实现,才恍然大悟:又是Java强大的反射机制。如图 4-1。

图 4-1

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窝在角落里学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值