Java序列化与反序列化

1. 序列化与反序列化

序列化:将对象转化成可存储或传输的格式的过程。

反序列化:序列化的逆向过程。

2. Serializable

如果对象需要序列化,只需对象实现Serializable接口即可。

比如:

import java.io.Serializable;
import lombok.Data;

@Data
public class User implements Serializable {
    private String name;
}

3. ObjectOutputStream 和 ObjectInputStream

  • 可通过ObjectOutputStream进行序列化;
  • 可通过ObjectInputStream进行反序列化。

3.1 序列化(ObjectOutputStream

@Test
public void testSerialize() {
    // 准备一个对象
    User user = new User();
    user.setName("tom");

    // 将对象存入文件
    // 未指定绝对路径,会在当前项目根目录下新建文件
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempfile"))) {
        oos.writeObject(user);
        System.out.println("已将对象序列化到文件中。" + user);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3.2 反序列化(ObjectInputStream

@Test
public void testDeserialize() {
    // 从文件中反序列化出对象
    File file = new File("tempfile");
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
        User newUser = (User) ois.readObject();
        System.out.println(newUser);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

4. serialVersionUID

实现序列化的对象需要指定一个序列化ID(serialVersionUID)。

作用是避免反序列化时出现InvalidClassException异常。

比如,上面的User对象序列化到文件中后,再修改User对象,添加一个age属性。

@Data
public class User implements Serializable {
    private String name;
    private Integer age;
}

然后再执行反序列化,便会报一个InvalidClassException异常。

这时,便需要给User对象添加一个serialVersionUID 。(可由IDE自动生成。)

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 3343871621195433478L;
    private String name;
    private Integer age;
}

重复上面的操作,便不会再报错。(注释掉age进行序列化、测试反序列化正常、再放开age测试反序列化也正常。)

5.定制序列化

5.1 writeObject 和 readObject

在序列化对象中添加writeObjectreadObject方法,可定制序列化对象具体哪些属性。

比如,

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 3343871621195433478L;
    private String name;
    private Integer age;
    private String hobit;

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.writeObject(name);
        oos.writeObject(hobit);
    }

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
        name = (String) ois.readObject();
        hobit = (String) ois.readObject();
    }
}

上面代码只序列化Usernamehobit属性,所以在反序列化的时候age的值就会变成空值。

测试案例如下:

// 序列化
@Test
public void testSerialize() {
    // 准备一个对象
    User user = new User();
    user.setName("tom");
    user.setAge(11);
    user.setHobit("read");

    // 将对象存入文件
    // 未指定绝对路径,会在当前项目根目录下新建文件
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempfile"))) {
        oos.writeObject(user);// -1636395914685360415
        System.out.println("已将对象序列化到文件中。" + user);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 反序列化
@Test
public void testDeserialize() {
    // 从文件中反序列化出对象
    File file = new File("tempfile");
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
        User newUser = (User) ois.readObject();
        System.out.println(newUser);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

执行序列化输出:已将对象序列化到文件中。User(name=tom, age=11, hobit=read)

执行反序列化输出:User(name=tom, age=null, hobit=read)

5.2 transient

使用transient修饰的属性,可以阻止该属性被序列,从而达到定制化序列化的目的。

比如上面的案例是阻止age被序列化,可使用transient关键字实现。

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 3343871621195433478L;
    private String name;
    private transient Integer age;
    private String hobit;
}

这样看起来清爽多了。

6. Externalizable

使用Externalizable接口也能支持对象序列化,Externalizable接口是Serializable接口的子类。

区别是实现Externalizable接口必须实现writeExternalreadExternal方法指定对象哪些属性需要序列化。

示例如下:

@Data
public class UserSecond implements Externalizable {
    private static final long serialVersionUID = 7209638685797540837L;
    private String name;
    private Integer age;
    private String hobit;

    @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();
    }
}

测试案例:

// 序列化
@Test
public void testExternalizable() {
    // 准备一个对象
    UserSecond user = new UserSecond();
    user.setName("tom");
    user.setAge(15);
    user.setHobit("read");

    // 将对象存入文件
    // 未指定绝对路径,会在当前项目根目录下新建文件
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempfile"))) {
        oos.writeObject(user);// -1636395914685360415
        System.out.println("testExternalizable..." + user);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 反序列化
@Test
public void testDeExternalizable() {
    // 从文件中反序列化出对象
    File file = new File("tempfile");
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
        UserSecond newUser = (UserSecond) ois.readObject();
        System.out.println(newUser);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

执行序列化输出:testExternalizable...UserSecond(name=tom, age=15, hobit=read)

执行反序列化输出:UserSecond(name=tom, age=15, hobit=null)


文章作者: 叶遮沉阳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶遮沉阳 !
  目录