Java序列化

1、先举个例子,包含以下几个类
public class Person implements Serializable {

    private String name;
    private int age;
//get  set  toString省略
}
public class Client {
    public static void main(String[] args) throws Exception{
        Person person = new Person();
        person.setAge(123);
        person.setName("里斯");
        SerializationUtil.writeObject(person);
        Person person2 = (Person) SerializationUtil.readObject();
        System.out.println(person2);
    }
}
public class SerializationUtil {
    private static final String FILE_NAME = "C:/serialization.txt";

    public static void writeObject(Serializable s) throws FileNotFoundException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
        oos.writeObject(s);
        oos.close();
    }

    public static Object readObject() throws FileNotFoundException, IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME));
        Object o = ois.readObject();
        ois.close();
        return o;
    }
}
2、首先说下serialVersionUID是干嘛用的
一个类实现Serializable的目的是为了可持久化,比如网络传输或本地存储,上边的实体类Person没有serialVersionUID会有什么问题呢?直接执行main方法输出如下:
Person [name=里斯, age=123]
一切正常,现在在person类中增加属性
 private String address;
再次读取文件中的对象
public class Client {
    public static void main(String[] args) throws Exception{
//        Person person = new Person();
//        person.setAge(123);
//        person.setName("里斯");
//        SerializationUtil.writeObject(person);
        Person person2 = (Person) SerializationUtil.readObject();
        System.out.println(person2);
    }
}
抛出异常
Exception in thread "main" java.io.InvalidClassException: com.tz.serializable.Person; local class incompatible: stream classdesc serialVersionUID = -7123988162792518014, local class serialVersionUID = 4484202720629602315
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
	at com.tz.serializable.SerializationUtil.readObject(SerializationUtil.java:30)
	at com.tz.serializable.Client.main(Client.java:17)
异常信息里表示本地serialVersionUID = 4484202720629602315而读取到的文件里的serialVersionUID = -7123988162792518014,这两个serialVersionUID是哪里来的呢
这个是编译器在编译的时候帮你生成的,生成的依据是通过包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等很多因素计算出来的,基本上计算出来的值是唯一的。
JVM在反序列化时会比较数据流中的serialVersionUID和类中的serialVersionUID是否一致,一致表示类在传输或存储过程中没有变化,可以把数据流转为实例对象。不一致的话就会抛出上边的异常。
所以手动加上一个serialVersionUID会省去很多不必要的麻烦,下次如果生产端person类增加了属性address而消费端未能及时更新时所带来的问题只是新增的属性在消费端不能读取。
3、相关的几个关键字
1)transient:保证被该关键字修饰的变量不被序列化
2)static:也是不会被序列化,大概原因是因为静态变量所在内存位置与一般变量不同,序列化时不会被写入流中
3)final:会被序列化,但是在反序列化时会重新计算变量值(只限于简单对象:8个基本类型、数组、字符串——字符串情况有点复杂,不通过new关键字生成String对象的情况下,final变量的赋值与基本类型相同),但是不能方法赋值,不能通过构造函数赋值
4、举个栗子
public class Person implements Serializable {

    /** */
    private static final long serialVersionUID = -1940095792507660710L;
    
    private String name;
    private int age;
    private String address;
    private static String staticValue = "staticValue";
    private final String finalValue = "finalValue";
    private static final String sfValue = "sfValue";
    private transient String transientValue = "transientValue";
//省略get set
@Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", address=" + address + ", finalValue=" + finalValue
                + ", transientValue=" + transientValue + ", staticValue=" + staticValue + ", sfValue=" + sfValue + "]";
    }

}
public class Client {
    public static void main(String[] args) throws Exception{
        Person person = new Person();
        person.setAge(123);
        person.setName("里斯");
        SerializationUtil.writeObject(person);
        Person person2 = (Person) SerializationUtil.readObject();
        System.out.println(person2);
    }
}

main方法执行结果

Person [name=里斯, age=123, address=null, finalValue=finalValue, transientValue=null, staticValue=staticValue, sfValue=sfValue]

transientValue=null可以理解, finalValue=finalValue, staticValue=staticValue, sfValue=sfValue,这三个的值是怎么来的呢。首先finalValue和sfValue是在反序列化时重新计算得来的,而staticValue是静态的Person被调用时就赋值了,把上边两个类改一下

    private String name;
    private int age;
    private String address;
    private static String staticValue = "staticValue2";
    private final String finalValue = "finalValue2";
    private static final String sfValue = "sfValue2";
    private transient String transientValue = "transientValue2";
public class Client {
    public static void main(String[] args) throws Exception{
//        Person person = new Person();
//        person.setAge(123);
//        person.setName("里斯");
//        SerializationUtil.writeObject(person);
        Person person2 = (Person) SerializationUtil.readObject();
        System.out.println(person2);
    }
}

本地person类初始值已经变化了,main方法直接从文件读取数据执行结果如下

Person [name=里斯, age=123, address=null, finalValue=finalValue2, transientValue=null, staticValue=staticValue2, sfValue=sfValue2]
finalValue,staticValue,sfValue都已经是本地修改后的Person类的初始值了,与文件中存储的数据没关系了

5、关于final重新计算的问题,再举个栗子

person类改成这样

    private static final long serialVersionUID = -1940095792507660710L;

    private String name;
    private int age;
    private String address;
    private final String finalValue;

    public Person() {
        finalValue = "通过构造器重新赋值";
    }

执行结果


修改person类

    private static final long serialVersionUID = -1940095792507660710L;

    private String name;
    private int age;
    private String address;
    private final String finalValue;

    public Person() {
        finalValue = "通过构造器重新赋值2222";
    }

再次读取


并没有重新计算为修改后的【通过构造器重新赋值2222】这是什么原因呢?

反序列化的执行过程是这样的:JVM从数据流中获取一个Object对象,然后根据数据流中的类文件描述信息查看,发现是final变量,需要重新计算,于是引用person类中的finalValue值,此时JVM发现变量没有赋值,于是停止初始化而保持原值状态,也就是最先序列化到文件中的【通过构造器重新赋值】(摘自《编写高质量代码——改善Java程序的151个建议》)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值