探究 Serializable接口和serialversionUID 意义及用途

问题描述

我们在实体类中实现了Serializable这个接口,那么这个接口到底有什么用?另外还定义了个serialVersionUID变量,这个变量又有什么作用呢?探究 Serializable接口和serialversionUID 意义及用途_第1张图片

什么是Serializable接口 

一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。

什么是序列化?

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

https://baike.baidu.com/item/%E5%BA%8F%E5%88%97%E5%8C%96/2890184?fr=aladdin

序列化的目的

1、以某种存储形式使自定义对象持久化;

2、将对象从一个地方传递到另一个地方。

3、使程序更具维护性。

Java编程中的序列化

序列化的实现方法

把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要自己去通过java把相应的对象写成转换成字节流。对于这种通用的操作,我们为什么不使用统一的格式呢?没错,这里就出现了java的序列化的概念。在Java的OutputStream类下面的子类ObjectOutputStream类就有对应的WriteObject(Object object) 其中要求对应的object实现了java的序列化的接口。

在使用tomcat开发JavaEE相关项目的时候,我们关闭tomcat后,相应的session中的对象就存储在了硬盘上,如果我们想要在tomcat重启的时能够从tomcat上面读取对应session中的内容,那么保存在session中的内容就必须实现相关的序列化操作,还有jdbc加载驱动用的就是反序列化,将字符串变为对象。

序列化操作

把对象转换为字节序列(二进制数据)的过程称为对象的序列化

public class Book implements Serializable{
    //序列化类:java.ioObjectOutputStream将对象变为指定的二进制数据
    private static final long serialVersionUID = 1L;
    private String title;
    private double price;
    public Book(String tit,double pri){
        this.title=tit;
        this.price=pri;
    }
  
    public String toString() {
        Map hashMap = new HashMap();
        hashMap.put("Computer base", "6.2");
        hashMap.put("Computer webpage",  "45.2");
        hashMap.put("Java", "105.2");
        hashMap.put("Andriod", "89.2");
         
        //第一种:使用keySet()方法遍历哈希表hashMap中的一些元素
          System.out.println("通过keySet()方法遍历key和value:");
          Set keys = hashMap.keySet();
          for (String key : keys) {
              String value= hashMap.get(key);
              System.out.println("BookName:"+ key + " ,BookPrice:" + value);
          }
        return "BookName:"+this.title+" ,BookPrice:"+this.price;
    }
}

反序列化

将二进制数据恢复为原对象,构造方法:ObjectInputStream(InputStream in),方法: Object readObject() 从 ObjectInputStream 读取对象

public class Serialization {
    public static File file = null;
    public static void main(String[] args) throws Exception, IOException {
        file = new File("serialize.doc");
        //序列化到指定的文本
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(new Book("Java Development",45.3));     
     
             oos.flush();
             oos.close();
              
         file = new File("serialize.doc");
        //反序列化到指定的文本
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
        Object obj=ois.readObject();
        Book book=(Book) obj;
        System.out.println("\n By using the byte stream serialization operation, "
                + "we can see the following information:\n"+book);
        ois.close();
        }
    }

无序列化

类某些属性不需要序列化,以上序列化和反序列化实现了的对象序列化,但是可以发现,操作时是将整个对象的所有属性序列化,那么transient关键字可以将某些内容不需要保存,就可以通过transient关键字来定义:private transient String title;此时title属性无法被序列化

实体类的序列化

为什么需要序列化?

当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化(或将对象存储在存储介质中,以便在下次使用的时候,可以很快捷的重建一个副本,即When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object.),以便将来使用时都需要把对象进行序列化。序列化的目的上面已提。

为什么pojo对象需要继承Serializable接口

在开发过程中,实体并没有实现序列化,但同样可以将数据保存到mysql、Oracle数据库中,为什么非要序列化才能存储呢?

实际上 Serializable接口 是启用其序列化功能的接口。实现java.io.Serializable 接口的类是可序列化的。没有实现此接口的类将不能使它们的任意状态被序列化或逆序列化。

如果打开过Serializable接口的源码,就会发现,这个接口其实是个空接口,那么这个序列化操作,其实,看一下接口的注释说明就知道,当我们让实体类实现Serializable接口时,其实是在告诉JVM此类可被序列化,可被默认的序列化机制序列化。

Serializable接口是一个里面什么都没有的接口,它的源代码是public interface Serializable{},即什么都没有。

如果一个接口里面什么内容都没有,那么这个接口是一个标识接口比如,一个学生遇到一个问题,排错排了几天也没解决,此时,她举手了(示意我去帮他解决),然后我过去,帮他解决了,那么这个举手其实就是一个标识,自己不能解决的问题标示我去帮他解决,在Java中的这个Serializable接口是给JVM看的,告诉JVM,我不做这个类的序列化了,你(JVM)给我序列化,序列化就是变成二进制流,比如云计算、Hadoop,特别是Hadoop完全就是分布式环境,那么就要涉及到对象要在网络中传输,里面的全是二进制流,当然你来做这个序列化操作也可以,但是这个类里面可能还有一个类,如果你把外面的类对象Person变成二进制,那么里面也要序列化(这要用到深度遍历,很麻烦),干脆告诉JVM,让他来帮你做。

serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。

 * @author  unascribed
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

为什么要定义serialversionUID变量

Serializable接口的说明

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification.  However, it is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected InvalidClassExceptions during deserialization.  Therefore, to guarantee a consistent serialVersionUID value across different java compiler implementations, a serializable class must declare an explicit serialVersionUID value.  It is also strongly advised that explicit serialVersionUID declarations use the private modifier where possible, since such declarations apply only to the immediately declaring class--serialVersionUID fields are not useful as inherited members. Array classes cannot declare an explicit serialVersionUID, so they always have the default computed value, but the requirement for matching serialVersionUID values is waived for array classes.

如果可序列化类没有显式声明serialVersionUID,则序列化运行时将根据Java(TM)对象序列化规范中所述的类的各个方面计算该类的默认serialVersionUID值。 但是, 强烈建议所有可序列化的类都明确声明serialVersionUID值,因为默认的serialVersionUID计算对类详细信息非常敏感,这可能会因编译器实现而异,因此可能会在反InvalidClassException化期间导致InvalidClassException的InvalidClassException。 因此,为了保证不同Java编译器实现之间的一致的serialVersionUID值,一个可序列化的类必须声明一个显式的serialVersionUID值。 还强烈建议,显式的serialVersionUID声明在可能的情况下使用private修饰符,因为这种声明仅适用于立即声明的类 - serialVersionUID字段作为继承成员无效。 数组类不能声明一个显式的serialVersionUID,所以它们总是具有默认的计算值,但是对于数组类,放弃了匹配serialVersionUID值的要求。

从说明中可以看到,如果我们没有自己声明一个serialVersionUID变量,接口会默认生成一个serialVersionUID,但是,强烈建议用户自定义一个serialVersionUID,因为默认的serialVersinUID对于class的细节非常敏感,反序列化时可能会导致InvalidClassException这个异常。

serialVersionUID的用途(多个回答)

一、serialVersionUID如果不指定的后果

serialVersionUID如果不指定会出现什么样的后果?如果指定了以后后边的值又代表着什么意思呢?既然系统指定了这个字段,那么肯定是有它的作用的。

这个serialVersionUID是用来辅助对象的序列化与反序列化的,原则上序列化后的数据当中的serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误。

https://baijiahao.baidu.com/s?id=1633305649182361563&wfr=spider&for=pc

二、serialVersionUID有何用途?如果没定义会有什么问题?

解:序列化是将对象的状态信息转换为可存储或传输的形式的过程。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。

而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把JAVA对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。

但是,虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致,即serialVersionUID要求一致。

在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即使InvalidCastException。这样做是为了保证安全,因为文件存储中的内容可能被篡改。

当实现java.io.Serializable接口的类没有显式地定义一个serialVersionUID变量时候,JAVA序列化机制会根据编译的Class自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果Class文件没有发生变化,就算再编译多次,serialVersionUID也不会变化的。但是,如果发生了变化,那么这个文件对应的serialVersionUID也就会发生变化。

基于以上原理,如果我们一个类实现了Serializable接口,但是没有定义serialVersionUID,然后序列化。在序列化之后,由于某些原因,我们对该类做了变更,重新启动应用后,我们相对之前序列化过的对象进行反序列化的话就会报错。

https://www.cnblogs.com/crazypokerk/p/9290812.html 

 


学习整理笔记,参考路径:

https://baijiahao.baidu.com/s?id=1633305649182361563&wfr=spider&for=pc

https://www.cnblogs.com/crazypokerk/p/9290812.html

https://www.cnblogs.com/wangenxian/p/11003221.html

 


待看文章:

序列化和反序列化的底层实现原理是什么?

https://blog.csdn.net/xlgen157387/article/details/79840134

你可能感兴趣的:(Java,java)