【转载】深入理解Java中的序列化(Serializable)

出处:https://blog.csdn.net/leixingbang1989/article/details/50556966

以前一直搞不明白序为什么要设计序列化这样一个接口,今天看了下别人的博客以及对应的视频教程,总算搞明白了,特此写下此篇博客。

一 序列化是干什么的?

我们知道,在jvm中引用数据类型存在于栈中,而new创建出的对象存在于堆中。如果电脑断电那么存在于内存中的对象就会丢失。那么有没有方法将对象保存到磁盘(对象持久化存储)或通过网络传输到远处的其他地方呢?

答案是可以,但是我们必须要求所有支持持久化存储的类实现Serializable接口。原因是,jvm不仅需要考虑对象存储到硬盘等其他介质,还需要考虑将其读取(反序列化)出来。

如果读取在硬盘中的对象不能被创建它的类识别,岂不是就会产生问题?序列化就是起到告诉JVM,这个对象是由哪个类创建的。

 
来看一下java源码中的解释:The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded  classes for that object that are compatible with respect to serialization.

对于jvm来说,要进行持久化的类必须要有个标记,相当于一个通行证,只有持有这个通行证,jvm才让类创建的对象进行持久化。这个通行证就是Serializable接口,这个接口将类与一个称为serialVersionUID的变量关联起来,这个serialVersionUID就是在反序列化中用来确定由哪个类来加载这个对象。

二 实现Serializable接口的小例子

首先来看一个简单的小例子,这个例子的作用就是将一个学生存储到本地磁盘中,然后再将其读取出来:

public class student implements Serializable {  
  
    public static  String countryName="china";  
    private int id;  
    private String name;  
    private String sex;  
  
    public String getSex() {  
  
        return sex;  
    }  
  
    public void setSex(String sex) {  
        this.sex = sex;  
    }  
  
    public int getId() {  
        return id;  
    }  
  
    public void setId(int id) {  
        this.id = id;  
    }  
  
    @Override  
    public String toString() {  
        return "student{" +  
                "id=" + id +  
                ", name='" + name + '\'' +  
                '}';  
    }  
  
    public String getName() {  
        String s="adaf";  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
}  
import java.io.*;  
  
public class ObjectStreamDemo {  
    public static void main(String[] args) {  
        //writeObj();  
        readObj();  
    }  
    public static  void writeObj()  
    {  
        student s=new student();  
        s.setId(8);  
        s.setName("张三");  
        s.countryName="USA";  
        try {  
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("c:\\obj.txt"));  
            oos.writeObject(s);  
            oos.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
    public  static  void  readObj()  
    {  
        try {  
            ObjectInputStream ooi=new ObjectInputStream(new FileInputStream("c:\\obj.txt"));  
            try {  
                Object obj=ooi.readObject();  
                student s=(student)obj;  
                //person s=(person)obj;  
                System.out.println("id:"+s.getId()+",name:"+s.getName()+s.countryName);  
            } catch (ClassNotFoundException e) {  
                e.printStackTrace();  
            }  
            ooi.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

可以看到程序正常运行,但是我们给student类添加一个成员变量private string school,那么则在读取时候抛出异常:

java.io.InvalidClassException:com.lei.lock.student; local class incompatible: stream classdescserialVersionUID = 5039200702392716702, local class serialVersionUID =-858199357477413536

大意为:本地类不匹配:保存在本地的文件描述类序列号为5039200702392716702 ,而当前类的序列号为-858199357477413536。因此我们可以得出结论,如果不指定serialVersionUID,那么其值是和类中的成员变量相关联的。

同时需要注意,持久化的数据均为存在于堆中的数据,static类型的数据存在于方法区中,不能被持久化。如果想不持久化某个成员变量,则需要在成员变量加上关键字transient

例如:

package com.lei.lock;/* 
*@author leixingbang_sx 
*Mail:leixingbang_sx@qiyi.com 
*@create 2016/1/21 15:04 
*desc  学生信息   
*/  
  
import java.io.Serializable;  
/*在写入的文件当中,会保存着写入对象的类的信息,我们可以打开看一下。 
*序列化只能存储在堆里的数据,不能存储在方法区的数据 
*没有方法的接口,称之为标记接口,告诉别人,我具备序列化的资格,相当于盖章作用 
* 序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,用于给编译器做标识 
* 其数字签名实际上是根据其成员变量的name id(包含其对应的属性、类型 private String int) 
* 该接口给类添加了serialVersionUID,假设类A 序列号为12L 产生了一个对象a,a被写入了磁盘当中,这时候,类A被修改,当被修改 
* 之后,类A的序列号也会对应发生改变 
* 该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。 
* 如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同, 
* 则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段) 
* 显式声明其自己的 serialVersionUID: 
* * */  
public class student implements Serializable {  
  
    public static  String countryName="china";  
    private int id;  
    private String name;  
    private String sex;  
    private String school;  
    //如果想对非静态的数据也不想序列化,则需要加入关键字  
    transient int height;//身高不存储到文件系统或者数据库中  
    public static String getCountryName() {  
        return countryName;  
    }  
  
    public static void setCountryName(String countryName) {  
        student.countryName = countryName;  
    }  
  
    public int getHeight() {  
        return height;  
    }  
  
    public void setHeight(int height) {  
        this.height = height;  
    }  
  
    public String getSex() {  
  
        return sex;  
    }  
  
    public void setSex(String sex) {  
        this.sex = sex;  
    }  
  
    public int getId() {  
        return id;  
    }  
  
    public void setId(int id) {  
        this.id = id;  
    }  
  
    @Override  
    public String toString() {  
        return "student{" +  
                "id=" + id +  
                ", name='" + name + '\'' +  
                '}';  
    }  
  
    public String getName() {  
        String s="adaf";  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
}  
package com.lei.lock;/* 
*@author leixingbang_sx 
*Mail:leixingbang_sx@qiyi.com 
*@create 2016/1/21 15:01 
*desc  对象数据流测试例子   
*/  
  
import java.io.*;  
  
public class ObjectStreamDemo {  
    public static void main(String[] args) {  
        //writeObj();  
        readObj();  
    }  
    public static  void writeObj()  
    {  
        student s=new student();  
        s.setId(8);  
        s.setName("张三");  
        s.countryName="USA";  
        s.setHeight(11);  
        try {  
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("c:\\obj.txt"));  
            oos.writeObject(s);  
            oos.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
    public  static  void  readObj()  
    {  
        try {  
            ObjectInputStream ooi=new ObjectInputStream(new FileInputStream("c:\\obj.txt"));  
            try {  
                Object obj=ooi.readObject();  
                student s=(student)obj;  
                //person s=(person)obj;  
                System.out.println("id:"+s.getId()+",name:"+s.getName()+",countryName:"+s.countryName+",height:"+s.getHeight());  
            } catch (ClassNotFoundException e) {  
                e.printStackTrace();  
            }  
            ooi.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

三 自定义serialVersionUID

   既然是由serialVersionUID来决定由哪个类来加载存在于文件中的对象,那么我们能不能指定serialVersionUID的数值,使得其不再与类的成员变量相关联呢?答案是肯定的。

例如:

需要注意的地方是,serialVersionUID不是决定由哪个类加载硬盘文件中的唯一因素,类的包名、类的名称都有关联。如果不一致,也会出现类型转化错误。原因是类的包名,类名已经被写入了文件当中。

public class student implements Serializable {  
  
    public static  String countryName="china";  
    private int id;  
    private String name;  
    private String sex;  
  
    private String school;  
    private static final long serialVersionUID = -5182532647273106745L;  
    //如果想对非静态的数据也不想序列化,则需要加入关键字  
    transient int height;//身高不存储到文件系统或者数据库中  
    public static String getCountryName() {  
        return countryName;  
    }  
  
    public static void setCountryName(String countryName) {  
        student.countryName = countryName;  
    }  
  
    public int getHeight() {  
        return height;  
    }  
  
    public void setHeight(int height) {  
        this.height = height;  
    }  
  
    public String getSex() {  
  
        return sex;  
    }  
  
    public void setSex(String sex) {  
        this.sex = sex;  
    }  
  
    public int getId() {  
        return id;  
    }  
  
    public void setId(int id) {  
        this.id = id;  
    }  
  
    @Override  
    public String toString() {  
        return "student{" +  
                "id=" + id +  
                ", name='" + name + '\'' +  
                '}';  
    }  
  
    public String getName() {  
        String s="adaf";  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
}  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值