序列化与反序列化和transient瞬态关键字

本文介绍了Java中的对象序列化和反序列化机制,包括使用ObjectOutputStream和ObjectInputStream进行操作。序列化时,对象必须实现Serializable接口,否则会抛出NotSerializableException。transient关键字用于标记不希望序列化的属性。反序列化时,若class文件修改,可能导致InvalidClassException。serialVersionUID用于确保类版本匹配。

1.概述

Java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。

序列化:对象转换为字节 ObjectOutputStream

反序列化:字节重构为对象 ObjectInputStream

2.ObjectOutputStream类

java.io.ObjectOutputStream类继承自OutputStream,是对象的序列化流,把对象以流的方式写入到文件中保存。

构造方法(红色为常用)创建写入指定OutputStream的ObjectOutputStream。

构造方法摘要
protected ObjectOutputStream()
          为完全重新实现 ObjectOutputStream 的子类提供一种方法,让它不必分配仅由 ObjectOutputStream 的实现使用的私有数据。
 ObjectOutputStream(OutputStream out)
          创建写入指定 OutputStream 的 ObjectOutputStream。

 参数:OutputStream out:字节输出流

特有成员方法:将指定的对象写入ObjectOutputStream。

voidwriteObject(Object obj)
          将指定的对象写入 ObjectOutputStream。

 使用步骤:

1.创建ObjectOutputStream对象,构造方法中传递字节输出流。

2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中。

3.释放资源。

public class Person {
    private String name;
    private int age;

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

    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;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }
}
// 1.创建ObjectOutputStream对象,构造方法中传递字节输出流。
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a\\e.txt"));
// 2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中。
Person person = new Person("易烊千玺",18);
oos.writeObject(person);
// 3.释放资源。
oos.close();

运行截图:

可以看到例子中序列化(和反序列化)的时候会抛出异常NotSerializableException,查看API文档可以看到,即当实例需要具有序列化接口时,抛出此异常。序列化运行时或实例的类会抛出此异常。参数应该为类的名称。

通俗理解就是需要类通过实现java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。

Serializable接口也叫标记型接口,要进行序列化和反序列化的必须实现Serializable接口,就会给类添加一个标记,当我们进行序列化和反序列化的时候,就会检测类上的是否有这个标记,如果有就可以序列化和反序列化;如果没有,就会抛出NotSerializableException异常。

public class Person implements Serializable {
    private String name;
    private int age;

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

    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;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }
}

2.ObjectInputStream类

java.io.ObjectInputStream类继承自InputStream,是对象的反序列化流,把文件中以字节保存的对象信息以流的形式读取到内存中。

构造方法(红色为常用)创建写入指定InputStream的ObjectInputStream。

构造方法摘要
protected ObjectInputStream()
          为完全重新实现 ObjectInputStream 的子类提供一种方式,让它不必分配仅由 ObjectInputStream 的实现使用的私有数据。
 ObjectInputStream(InputStream in)
          创建从指定 InputStream 读取的 ObjectInputStream。

参数:InputStream in:字节输入流

特有成员方法:读取对象

 ObjectreadObject()
          从 ObjectInputStream 读取对象。

使用步骤:

1.创建ObjectInputStream对象,构造方法中传递字节输入流。

2.使用ObjectInputStream对象中的方法readObject,读取保存对象文件。

3.释放资源。

4.使用读取出来的对象(打印)

private static void in() throws IOException, ClassNotFoundException {
    // 1.创建ObjectInputStream对象,构造方法中传递字节输入流。
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a\\e.txt"));
    // 2.使用ObjectInputStream对象中的方法readObject,读取保存对象文件。
    Object object = ois.readObject();
    // 3.释放资源。
    ois.close();
    // 4.使用读取出来的对象(打印)
    System.out.println(object.toString());
}

注意:readObject方法声明抛出了ClassNotFoundException(class文件找不到异常),当不存在这个对象class文件时抛出此异常。

4.序列化操作

序列化的前提:

1.类必须实现Serializble

2.必须存在类对应的class文件

即一个对象想要序列化,必须满足两个条件:

(1)该类必须实现java.io.Serializable接口,Serializable是一个标记接口,不是先此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException。

(2)该类的所有属性必须是可序列化的,如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。

对比知识点:static关键字(静态关键字)静态优先于非静态加载到内存中(静态优先于对象进入到内存中,被static修饰的成员变量不能被序列化(能读写文件,只是内容与赋值的不同),序列化的都是对象,静态的属于类不属于对象)

举例,在之前的代码基础上修改:

public class Person implements Serializable {
    private String name;
    private static int age; // static修饰
...

运行截图:

会发现输出age=0,不根据赋值内容而变,而是默认初始化的0。

注意!!!!:将序列化和反序列化分别使用两个程序进行,同一个程序会导致static变量已经被JVM加载,取出来的值就是自己赋值的。

而被transient关键字(瞬态关键字)修饰的成员变量,也不能被序列化(能读写文件,只是内容与赋值的不同)

 

public class Person implements Serializable {
    private String name;
    private transient int age; // transient修饰

...

运行截图: 

5.反序列化操作2

另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

(1)该类的序列版本号与从流中读取的类描述符的版本号不匹配;

(2)该类包含未知数据类型;

(3)该类没有可访问的无参数构造方法。

Serializable接口给需要序列化的类,提供了一个序列版本号serialVersionID,该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

每次修改类的定义,都会给class文件生成一个新的序列号,因此如果想要无论是否对类的定义进行修改 都不重新生成新的序列号,可以手动给类天机啊一个序列号。

格式:

在Serializable接口规定:可序列化类可以通过伤声明“serialVersionUID”的字段(该字段必须是静态static、最终final的long型字段)显式声明其自己的serialVersionUID。

static final long serialVersionUID = 42L;

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值