java序列化中的常量_Java-序列化
发布日期:2022-02-18 13:08:05 浏览次数:8 分类:技术文章

本文共 4861 字,大约阅读时间需要 16 分钟。

序列化的含义和意义

序列化指将Java对象转换成字节序列, 这些字节序列可以保存在磁盘上, 或者进行网络传输。反序列化即指将序列化后的字节序列重新恢复成对象。序列化机制使得对象可以脱离程序的运行而独立存在。

一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口,这个接口不包含任何方法或成员变量,只是一个标记。

序列化/反序列化

序列化通过ObjectOutputStream来实现,它可以把一个Java对象写入字节流。

当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名.

反序列化使用ObjectInputStream对象,它可以从字节流中读取一个Java对象。若反序列化时要使用readObject()读出多个对象,注意要与写入的顺序一致。

示例:

public class SerializableDemo {

public static void main(String[] args) throws Exception {

writeObj();

readObj();

}

private static void writeObj() throws Exception {

File file = new File("E:\\person.ser");

FileOutputStream out = new FileOutputStream(file);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);

Person person = new Person("王大锤", 25);

objectOutputStream.writeObject(person);

System.out.println("写入的对象:" + person);

objectOutputStream.close();

}

private static void readObj() throws Exception{

File file = new File("E:\\person.ser");

FileInputStream in = new FileInputStream(file);

ObjectInputStream objectInputStream = new ObjectInputStream(in);

Person person = (Person)objectInputStream.readObject();

System.out.println("读取的对象:" + person);

objectInputStream.close();

}

}

class Person implements Serializable{

private String name;

private int age;

public Person(String name, int age) {

System.out.println("Person's constructor");

this.name = name;

this.age = age;

}

//...省略getter、setter、toString

}

程序输出:

Person's constructor

写入的对象:Person{name='王大锤', age=25}

读取的对象:Person{name='王大锤', age=25}

可以看到反序列化时,Person的构造器并没有执行。反序列化时,是由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时将不被执行。

看另一个示例:

public static void main(String[] args) throws Exception {

ByteArrayOutputStream byteOut = new ByteArrayOutputStream();

ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOut);

Person person = new Person("张三", 20);

objectOutputStream.writeObject(person);

ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));

Person readPerson = (Person)objectInputStream.readObject();

System.out.println("反序列化得到的Person对象:" + readPerson);

System.out.println("反序列化得到的对象与原对象是否是同一对象:" + (person==readPerson));

}

输出:

反序列化得到的Person对象:Person{name='张三', age=20}

反序列化得到的对象与原对象是否是同一对象:false

可见反序列化得到的对象与原来的person不是同一个对象,即反序列化得到的是一个新的对象。

serialVersionUID

每一个可序列化类都会带有一个long类型的serialVersionUID静态常量值,如果没有人为显式定义过serialVersionUID,那编译器会根据类的各种信息为它自动声明一个。

serialVersionUID是序列化前后类的唯一标识符,在反序列化时,jvm会把字节流中的序列号id和被序列化类的序列号id进行比较,只有两个id相同才能成功反序列化,否则抛出InvalidClassException异常。

若是没有人为指定序列号id,则若是改变类时(如添加新的成员变量),默认的serialVersionUID值将会改变。

示例:

执行上述例子的writeObj()方法后,在Person类定义中添加一个birth成员变量:private Date birth;

再执行readObj()进行反序列化,此时抛出异常:即字节流中的serialVersionUID与Person类的不同。

Exception in thread "main" java.io.InvalidClassException: io.stream.serializable.Person; local class incompatible: stream classdesc serialVersionUID = 5578959987866231607, local class serialVersionUID = -8466270221378179612

一般对于可序列化类,为了serialVersionUID的确定性,都要人为指定一个serialVersionUID值,可以使用idea的自动生成功能:IDEA自动生成serialVersionUID 。

反序列化时的异常

在反序列化时readObject()可能抛出的异常有:

ClassNotFoundException:没有找到对应的Class;

InvalidClassException:Class不匹配。

抛出ClassNotFoundException的情况:一台电脑上的Java程序把一个Java对象序列化以后,通过网络传给另一台电脑上的另一个Java程序,但是这台电脑的Java程序并没有定义该类,所以无法反序列化,抛出该异常。

抛出InvalidClassException的情况:如serialVersionUID中的示例情况。

序列化的特殊情况

当要序列化的对象中包含其他引用类型变量的引用时,该变量对应的类也要实现Serializable接口。

对同一个对象多次序列化,只有第一次序列化时JVM才会将对象转换为字节序列,之后的序列化只是直接输出一个序列化编号。且反序列化时多次使用readObject()读出的是同一个对象。

示例:

示例中的Person类同上。新增了一个ID类:

package stream.serializable;

import java.io.Serializable;

public class ID implements Serializable {

private static final long serialVersionUID = -5829455553074732547L;

private int number;

private Person person;

//...省略getter、setter、toString

}

创建一个Person对象person和一个ID对象id,id中的Person指向person。将person和id序列化。再反序列,可看到readId.getPerson()==readPerson返回true。

public static void main(String[] args) throws Exception {

File file = new File("E:\\id.ser");

FileOutputStream out = new FileOutputStream(file);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);

ID id = new ID();

Person person = new Person("王大锤", 30);

id.setNumber(1);

id.setPerson(person);

objectOutputStream.writeObject(id);

objectOutputStream.writeObject(person);

FileInputStream in = new FileInputStream(file);

ObjectInputStream objectInputStream = new ObjectInputStream(in);

ID readId = (ID) objectInputStream.readObject();

Person readPerson = (Person) objectInputStream.readObject();

System.out.println(readId);

System.out.println(readPerson);

System.out.print("readId.getPerson()==readPerson:");

System.out.print(readId.getPerson()==readPerson);

}

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

因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域。

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

transient修饰符的作用就是修饰不想被序列号的成员变量,一般是对于一些要保密的字段,如密码。对于被transient修饰的字段,在序列号时以null值填充(对于值类型,则使用0、false等默认值)。

还有一个点是序列化的受控和加强,具体见第3个参考链接中最后一个小节。

参考

---------------------------------------------------------

转载地址:https://blog.csdn.net/weixin_28871097/article/details/114726839 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:java设计模式中不属于创建型模式_23种设计模式第二篇:java工厂模式定义:工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式...
下一篇:mysql show命令用不了_MySQL show命令的用法 | 很文博客

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月24日 06时13分15秒