栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Jackson - 简述Java内置的序列化机制

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Jackson - 简述Java内置的序列化机制

文章目录
    • 1. 使用对象流实现序列化
    • 2. Serializable接口有何用?
    • 3. serialVersionUID号有何用?
    • 4. 两种特殊情况

1. 使用对象流实现序列化

序列化的原本意图是希望对一个Java对象作一下“变换”,变成字节序列,这样一来方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,另外变换成字节序列也更便于网络运输和传播,所以概念上很好理解:

  • 序列化:把Java对象转换为字节序列。
  • 反序列化:把字节序列恢复为原先的Java对象。

java内置的序列化机制仅仅适用于Java语言,与其他序列化方式,比如Json序列化,Xml序列化,Protobuf序列化方式没有任何关系。

如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现Serializable接口或者Externalizable接口之一。使用Serializable来实现序列化非常简单,主要让目标类实现Serializable标记接口即可,无须实现任何方法。一旦某个类实现了Serializable接口,该类的对象就是可序列化的。

(1)创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其他节点流的基础之上。

(2)调用ObjectOutputStream对象的writeObject()方法输出可序列化对象。

(3)调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型。

@Data
public class Student implements Serializable {
    private String name;
    private Integer age;
    private Integer score;
}
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serialize();
        deserialize();
    }

    private static void serialize() throws IOException {
        Student student = new Student();
        student.setName("linko");
        student.setAge( 18 );
        student.setScore( 1000 );

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("student.txt"));
        // 将Student对象写入输出流
        objectOutputStream.writeObject(student);
        objectOutputStream.close();

        System.out.println("序列化成功!已经生成student.txt文件");
        System.out.println("==============================================");
    }

    private static void deserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream( new FileInputStream("student.txt"));
        Student student = (Student) objectInputStream.readObject();
        objectInputStream.close();

        System.out.println("反序列化结果为:");
        System.out.println( student );
    }
}

测试结果:

序列化成功!已经生成student.txt文件
==============================================
反序列化结果为:
Student(name=linko, age=18, score=1000)
2. Serializable接口有何用?

上面在定义Student类时,实现了一个Serializable接口,然而当我们点进Serializable接口内部查看,发现它竟然是一个空接口,并没有包含任何方法!

public interface Serializable {
}

试想,如果上面在定义Student类时忘了加implements Serializable时会发生什么呢?

实验结果是:此时的程序运行会报错,并抛出NotSerializableException异常:

我们按照错误提示,由源码一直跟到ObjectOutputStream的writeObject0()方法底层一看,才恍然大悟:

// remaining cases
if (obj instanceof String) {
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    writeEnum((Enum) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
} else {
    if (extendedDebugInfo) {
        throw new NotSerializableException(
            cl.getName() + "n" + debugInfoStack.toString());
    } else {
        throw new NotSerializableException(cl.getName());
    }
}

如果一个对象既不是字符串、数组、枚举,而且也没有实现Serializable接口的话,在序列化时就会抛出NotSerializableException异常!

原来Serializable接口也仅仅只是做一个标记用!!!

它告诉代码只要是实现了Serializable接口的类都是可以被序列化的!然而真正的序列化动作不需要靠它完成。

3. serialVersionUID号有何用?

相信你一定经常看到有些类中定义了如下代码行,即定义了一个名为serialVersionUID的字段:

private static final long serialVersionUID = -4392658638228508589L;

继续来做一个简单实验,还拿上面的Student类为例,我们并没有人为在里面显式地声明一个serialVersionUID字段。

首先,按照上面的方式将Student对象序列化到student.txt文件中,然后改变Student类,比如添加一个字段studentId:

@Data
public class Student implements Serializable {
    private String name;
    private Integer age;
    private Integer score;
    private Integer studentId;
}

运行发现报错了,并且抛出了InvalidClassException异常:

Exception in thread "main" java.io.InvalidClassException: com.example.serializer.Student; 
local class incompatible: stream classdesc serialVersionUID = 8071311942157237447, 
local class serialVersionUID = -2800223051458730964
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
	at com.example.serializer.Main.deserialize(Main.java:28)
	at com.example.serializer.Main.main(Main.java:8)

这地方提示的信息非常明确了:序列化前后的serialVersionUID号码不兼容!

从这地方最起码可以得出两个重要信息:

1、serialVersionUID是序列化前后的唯一标识符
2、默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!

第1个问题: serialVersionUID序列化ID,可以看成是序列化和反序列化过程中的“暗号”,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程。

第2个问题: 如果在定义一个可序列化的类时,没有人为显式地给它定义一个serialVersionUID的话,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID,一旦像上面一样更改了类的结构或者信息,则类的serialVersionUID也会跟着变化!

所以,为了serialVersionUID的确定性,写代码时还是建议,凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID明确值!

4. 两种特殊情况

1、凡是被static修饰的字段是不会被序列化的

2、凡是被transient修饰符修饰的字段也是不会被序列化的

对于第一点,因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域也是理所应当的。

对于第二点,就需要了解一下transient修饰符的作用了。

如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段。

比如在之前定义的Student类中,加入一个密码字段,但是不希望序列化到txt文本,则可以:

@Data
public class Student implements Serializable {
    private String name;
    private Integer age;
    private Integer score;
    private transient String password;
}

这样在序列化Student类对象时,password字段会设置为默认值null,这一点可以从反序列化所得到的结果来看出:

序列化成功!已经生成student.txt文件
==============================================
反序列化结果为:
Student(name=linko, age=18, score=1000, password=null)

参考文章:https://mp.weixin.qq.com/s/0EfIUB9E-0Oh_Clwuxswuw

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/871677.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号