Java对象为啥要实现Serializable接口?

Java对象为啥要实现Serializable接口?



最近这段时间一直在忙着编写Java业务代码,麻木地搬着Ctrl-C、Ctrl-V的砖,在不知道重复了多少次定义Java实体对象时“implements Serializable”的C/V大法后,脑海中忽然冒出一个思惟(A):问了本身一句“Java实体对象为何必定要实现Serializable接口呢?”,关于这个问题,脑海中的另外一个思惟(B)立马给出了回复“竟然问这么幼稚和基础的问题,实现Serilizable接口是为了序列化啊!”,思惟(A):“哦,好吧!然而,而后呢?”html

此时思惟(B)陷入了沉默,忽然感受本身有点浅薄了,好像写了这么多年Java还真是没有太关注过Serializable这个接口!为何必定要实现Serializable接口?它的底层原理是什么?为何必定要序列化,序列化又是什么?关于这些问题,不知道各位读者朋友有没有过相似的问题,若是有那么咱们就在这篇文章中一块儿寻找答案吧!固然,若是你对这些问题都很清楚,也欢迎表达见解!java

Serializable接口概述

Serializable是http://java.io包中定义的、用于实现Java类的序列化操做而提供的一个语义级别的接口。Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。实现了Serializable接口的类能够被ObjectOutputStream转换为字节流,同时也能够经过ObjectInputStream再将其解析为对象。例如,咱们能够将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可使用表示对象及其数据的类型信息和字节在内存中从新建立对象。数据库

而这一点对于面向对象的编程语言来讲是很是重要的,由于不管什么编程语言,其底层涉及IO操做的部分仍是由操做系统其帮其完成的,而底层IO操做都是以字节流的方式进行的,因此写操做都涉及将编程语言数据类型转换为字节流,而读操做则又涉及将字节流转化为编程语言类型的特定数据类型。而Java做为一门面向对象的编程语言,对象做为其主要数据的类型载体,为了完成对象数据的读写操做,也就须要一种方式来让JVM知道在进行IO操做时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。编程

下面咱们能够经过例子来实现将序列化的对象存储到文件,而后再将其从文件中反序列化为对象,代码示例以下:api

先定义一个序列化对象User:数组

public class User implements Serializable { 
    private static final long serialVersionUID = 1L; 
 
    private String userId; 
    private String userName; 
 
    public User(String userId, String userName) { 
        this.userId = userId; 
        this.userName = userName; 
    } }

而后咱们编写测试类,来对该对象进行读写操做,咱们先测试将该对象写入一个文件:网络

public class SerializableTest { 
 
    /** 
     * 将User对象做为文本写入磁盘 
     */ 
    public static void writeObj() { 
        User user = new User("1001", "Joe"); 
        try { 
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/guanliyuan/user.txt")); 
            objectOutputStream.writeObject(user); 
            objectOutputStream.close(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    public static void main(String args[]) { 
        writeObj(); 
    } }

运行上述代码,咱们就将User对象及其携带的数据写入了文本user.txt中,咱们能够看下user.txt中存储的数据此时是个什么格式:mybatis

java.io.NotSerializableException: cn.wudimanong.serializable.User 
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) 
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) 
    at cn.wudimanong.serializable.SerializableTest.writeObj(SerializableTest.java:19) 
    at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:27)

咱们看到对象数据以二进制文本的方式被持久化到了磁盘文件中。在进行反序列化测试以前,咱们能够尝试下将User实现Serializable接口的代码部分去掉,看看此时写操做是否还能成功,结果以下:oracle

结果不出所料,果真是不能够的,抛出了NotSerializableException异常,提示非可序列化异常,也就是说没有实现Serializable接口的对象是没法经过IO操做持久化的。框架

接下来,咱们继续编写测试代码,尝试将以前持久化写入user.txt文件的对象数据再次转化为Java对象,代码以下:

public class SerializableTest { 
    /** 
     * 将类从文本中提取并赋值给内存中的类 
     */ 
    public static void readObj() { 
        try { 
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/guanliyuan/user.txt")); 
            try { 
                Object object = objectInputStream.readObject(); 
                User user = (User) object; 
                System.out.println(user); 
            } catch (ClassNotFoundException e) { 
                e.printStackTrace(); 
            } 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
 
 
    public static void main(String args[]) { 
        readObj(); 
    } }

经过反序列化操做,能够再次将持久化的对象字节流数据经过IO转化为Java对象,结果以下:

cn.wudimanong.serializable.User@6f496d9f

此时,若是咱们再次尝试将User实现Serializable接口的代码部分去掉,发现也没法再文本转换为序列化对象,报错信息为:

ava.io.InvalidClassException: cn.wudimanong.serializable.User; class invalid for deserialization 
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157) 
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862) 
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2038) 
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568) 
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428) 
    at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31) 
    at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:44)

提示非法类型转换异常,说明在Java中如何要实现对象的IO读写操做,都必须实现Serializable接口,不然代码就会报错!

序列化&反序列化

经过上面的阐述和示例,相信你们对Serializable接口的做用是有了比较具体的体会了,接下来咱们上层到理论层面,看下到底什么是序列化/反序列化。序列化是指把对象转换为字节序列的过程,咱们称之为对象的序列化,就是把内存中的这些对象变成一连串的字节(bytes)描述的过程。

而反序列化则相反,就是把持久化的字节文件数据恢复为对象的过程。那么什么状况下须要序列化呢?大概有这样两类比较常见的场景:1)、须要把内存中的对象状态数据保存到一个文件或者数据库中的时候,这个场景是比较常见的,例如咱们利用mybatis框架编写持久层insert对象数据到数据库中时;2)、网络通讯时须要用套接字在网络中传送对象时,如咱们使用RPC协议进行网络通讯时;

关于serialVersionUID

对于JVM来讲,要进行持久化的类必需要有一个标记,只有持有这个标记JVM才容许类建立的对象能够经过其IO系统转换为字节数据,从而实现持久化,而这个标记就是Serializable接口。而在反序列化的过程当中则须要使用serialVersionUID来肯定由那个类来加载这个对象,因此咱们在实现Serializable接口的时候,通常还会要去尽可能显示地定义serialVersionUID,如:

private static final long serialVersionUID = 1L;

在反序列化的过程当中,若是接收方为对象加载了一个类,若是该对象的serialVersionUID与对应持久化时的类不一样,那么反序列化的过程当中将会致使InvalidClassException异常。例如,在以前反序列化的例子中,咱们故意将User类的serialVersionUID改成2L,如:

private static final long serialVersionUID = 2L;

那么此时,在反序例化时就会致使异常,以下:

java.io.InvalidClassException: cn.wudimanong.serializable.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2 
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687) 
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880) 
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746) 
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037) 
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568) 
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428) 
    at cn.wudimanong.serializable.SerializableTest.readObj(SerializableTest.java:31) 
    at cn.wudimanong.serializable.SerializableTest.main(SerializableTest.java:44)

若是咱们在序列化中没有显示地声明serialVersionUID,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID值。可是,Java官方强烈建议全部要序列化的类都显示地声明serialVersionUID字段,由于若是高度依赖于JVM默认生成serialVersionUID,可能会致使其与编译器的实现细节耦合,这样可能会致使在反序列化的过程当中发生意外的InvalidClassException异常。所以,为了保证跨不一样Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。

此外serialVersionUID字段地声明要尽量使用private关键字修饰,这是由于该字段的声明只适用于声明的类,该字段做为成员变量被子类继承是没有用处的!有个特殊的地方须要注意的是,数组类是不能显示地声明serialVersionUID的,由于它们始终具备默认计算的值,不过数组类反序列化过程当中也是放弃了匹配serialVersionUID值的要求。

参考资料:

https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html?is-external=true

http://www.tutorialspoint.com/java/java_serialization.htm

相关文章
相关标签/搜索