Java序列化进阶:Java内置序列化的三种方式

Java序列化就是把Java对象按照一定的格式存到文件或者磁盘当中

序列化的进阶:即三种方式,任何一种方式都可以进行序列化和反序列化

如果将数据读写到文档,

一般通过 ObjectOutputStream 将数据写入到文件当中,就是一种序列化的过程;

通过 ObjectInputStream 将数据从文件中读出来,就是一种反序列化的过程

如果对数据不进行序列化系统就会抛出异常信息:java.io.NotSerializableException

查看ObjectOutputStream 的 writeObject0() 方法源码:

// 判断对象是否为字符串类型,如果是,则调用 writeString 方法进行序列化
if (obj instanceof String) {
    writeString((String) obj, unshared);
}
// 判断对象是否为数组类型,如果是,则调用 writeArray 方法进行序列化
else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
}
// 判断对象是否为枚举类型,如果是,则调用 writeEnum 方法进行序列化
else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
}
// 判断对象是否为可序列化类型,如果是,则调用 writeOrdinaryObject 方法进行序列化
else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
}
// 如果对象不能被序列化,则抛出 NotSerializableException 异常
else {
if (extendedDebugInfo) {
    throw new NotSerializableException(
        cl.getName() + "\n" + debugInfoStack.toString());
} else {
    throw new NotSerializableException(cl.getName());
}
}

ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException

1、实现Serializable接口

使用默认的序列化机制,即实现Serializable接口即可,不需要实现任何方法。

该接口没有任何方法,只是一个标记而已,告诉Java虚拟机该类可以被序列化了。然后利用ObjectOutputStream进行序列化和用ObjectInputStream进行反序列化。

注意:

该方式下序列化机制会自动保存该对象的成员变量 ,static成员变量和transient关键字修饰的成员变量不会被序列化保存。如:

import java.io.Serializable;
import java.util.List;

public class SerializeTest implements Serializable {
    private static final long serialVersionUID = 213424233;
    private String name;
    private static String teacher;
    private final int bornYear;
    private static final String mother="mother";
    private transient int age;
    private transient List<String> friends;
    SerializeTest(){
        bornYear  = 2010;
    }
@Override
    public String toString() {
        return "SerializeTest{" +
                "name='" + name + '\'' +
                ", bornYear=" + bornYear +
                ", teacher=" + teacher +
                ", age=" + age +
                ", friends=" + friends +
                '}';
    }
}

要序列化的对象

看看序列化和反序列化效果:

 SerializeTest test = new SerializeTest();
        test.setName("myName");
        test.setAge(18);
        List<String> friends = new ArrayList<String>();
        friends.add("friendOne");
        friends.add("friendTwo");
        test.setFriends(friends);
        test.setTeacher("myTeacher");

        try {
            //序列化到文件
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\user\\serialTest")));
            oos.writeObject(test);
            System.out.println("序列化:"+test.toString());
            test.setTeacher("myTeacher2");
            //从文件反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\user\\serialTest")));
            SerializeTest objRead = (SerializeTest) ois.readObject();
            System.out.println("序列化后:"+objRead.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

序列化和反序列化结果

序列化:SerializeTest{name='myName', bornYear=2000, teacher=myTeacher, age=18, friends=[friendOne, friendTwo]}
序列化后:SerializeTest{name='myName', bornYear=2000, teacher=myTeacher2, age=0, friends=null}

transient 的中文字义为"临时的"(论英语的重要性),它可以阻止字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null

这是最简单的一种方式,因为这种方式让序列化机制看起来很方便(然后,我们在进行对象序列化时,只需要使用ObjectOutputStream和ObjectInputStream的writeObject(object)方法和readObject()方法,就可以把传入的对象参数序列化和反序列化了,其他不用管)。有时候想自己来控制序列化哪些成员,还有如何保存static和transient成员?

再注意:

该方式下,反序列化时不会调用该对象的构造器,但是会调用父类的构造器,如果父类没有默认构造器则会报错。static字段是类共享的字段,当该类的一个对象被序列化后,这个static变量可能会被另一个对象改变,所以这就决定了静态变量是不能序列化的,但如果再加上final修饰,就可以被序列化了,因为这是一个常量,不会改变。

2、实现Externalizable接口

Externalizable接口是继承自Serializable接口的,我们在实现Externalizable接口时,必须实现writeExternal(ObjectOutput)和readExternal(ObjectInput)方法,在这两个方法下我们可以手动的进行序列化和反序列化那些需要保存的成员变量。

public class SerializeExternalTest implements Externalizable {
    private static final long serialVersionUID = 213424233;
    private String name;
    private static String teacher;
    private final int bornYear;
    private static final String mother="mother";
    private transient int age;
    private transient List<String> friends;
    SerializeExternalTest(){
        bornYear  = 2000;
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeUTF(teacher);
        out.writeInt(bornYear);
        out.writeUTF(mother);
        out.writeInt(age);
        out.writeObject(friends);
    }

    @Override
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
        this.name = in.readUTF();
        this.teacher = in.readUTF();
        //this.bornYear = in.readInt();final的不能改变值
        //this.mother = in.readUTF();final的不能改变值
        in.readInt();
        in.readUTF();
        this.age= in.readInt();
        this.friends = (List<String>) in.readObject();
    }
    public String getName() {
        return name;
    }

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

    public String getTeacher() {
        return teacher;
    }

    public void setTeacher(String teacher) {
        SerializeExternalTest.teacher = teacher;
    }

    public int getBornYear() {
        return bornYear;
    }

    public int getAge() {
        return age;
    }

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

    public List<String> getFriends() {
        return friends;
    }

    public void setFriends(List<String> friends) {
        this.friends = friends;
    }
    
}

Externalizable

public void externalizableT(){
        SerializeExternalTest test = new SerializeExternalTest();
        test.setName("myName");
        test.setAge(18);
        List<String> friends = new ArrayList<String>();
        friends.add("friendOne");
        friends.add("friendTwo");
        test.setFriends(friends);
        test.setTeacher("myTeacher");

        try {
            //序列化到文件
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\user\\serialTest")));
            oos.writeObject(test);
            //从文件反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\user\\serialTest")));
            SerializeTest objRead = (SerializeTest) ois.readObject();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

Externalizable结果

反序列化时,首先会调用对象的默认构造器(没有则报错,如果默认构造器不是public的也会报错),然后再调用readExternal方法。

这种方式一定要显式的序列化成员变量,使得整个序列化过程是可控制的,可以自己选择将哪些部分序列化。

3、实现Serializable接口并添加writeObject和readObject方法

实现Serializable接口,在该实现类中再增加writeObject方法和readObject方法。该方式要严格遵循以下两个方法的方法签名:

writeObject和readObject

在这两个方法里面需要使用stream.defaultWriteObject()序列化那些非static和非transient修饰的成员变量,static的和transient的变量则用**stream.writeObject(object)**显式序列化。

在序列化输出的时候,writeObject(object)会检查object参数,如果object拥有自己的writeObject()方法,那么就会使用它自己的writeObject()方法进行序列化。readObject()也采用了类似的步骤进行处理。如果object参数没有writeObject()方法,在readObject方法中就不能调用stream.readObject(),否则会报错。

public class SeriCtrolTest implements Serializable {
   private String a;
   private String b;
   private transient String c;
    SeriCtrolTest(String a,String b ,String c){
        this.a = "非瞬时默认实现"+a;
        this.b = "非顺时非默认实现"+b;
        this.c = "瞬时实现:"+c;
    }
    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        stream.writeObject(b);
        stream.writeObject(c);
    }
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        stream.readObject();
        b="null";
        c = stream.readObject().toString();
    }

    @Override
    public String toString() {
        return "SeriCtrolTest{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                '}';
    }
    public static void main(String[] args){
        SeriCtrolTest sCtrl = new SeriCtrolTest("test1","test2","test3");
        System.out.println("序列化之前");
        System.out.println(sCtrl);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(out);
            oos.writeObject(sCtrl);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        System.out.println("反序列化操作之后");
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());

    }
}

参考文献:

浅谈一下Java的Serializable 接口(序列化与反序列化)-CSDN博客

Java序列化进阶:Java内置序列化的三种方式-腾讯云开发者社区-腾讯云

相关推荐
尽兴-几秒前
Redis模拟延时队列 实现日程提醒
java·redis·java-rocketmq·mq
书埋不住我21 分钟前
java第三章
java·开发语言·servlet
boy快快长大23 分钟前
将大模型生成数据存入Excel,并用增量的方式存入Excel
java·数据库·excel
孟秋与你25 分钟前
【spring】spring单例模式与锁对象作用域的分析
java·spring·单例模式
菜菜-plus29 分钟前
java 设计模式 模板方法模式
java·设计模式·模板方法模式
萨达大30 分钟前
23种设计模式-模板方法(Template Method)设计模式
java·c++·设计模式·软考·模板方法模式·软件设计师·行为型设计模式
tian-ming32 分钟前
(十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器
java·开发语言·前端
不能只会打代码35 分钟前
大学课程项目中的记忆深刻 Bug —— 一次意外的数组越界
java·github·intellij-idea·话题博客
快意咖啡~41 分钟前
java.nio.charset.MalformedInputException: Input length = 1
java·开发语言·nio
IT枫斗者1 小时前
如何解决Java EasyExcel 导出报内存溢出
java·服务器·开发语言·网络·分布式·物联网