Java将对象保存到文件中/从文件中读取对象
Java类实现serializable
1.Java序列化与反序列化
Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。
2.为什么需要序列化与反序列化
我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
3.如何实现Java序列化与反序列化
1)JDK类库中序列化API
java.io.ObjectOutputStream:表示对象输出流
它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream:表示对象输入流
它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。
2)实现序列化的要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。
3)实现Java对象序列化与反序列化的方法
假定一个Student类,它的对象需要序列化,可以有如下三种方法:
方法一:若Student类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化
ObjectOutputStream采用默认的序列化方式,对Student对象的非transient的实例变量进行序列化。
ObjcetInputStream采用默认的反序列化方式,对对Student对象的非transient的实例变量进行反序列化。
方法二:若Student类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。
ObjectOutputStream调用Student对象的writeObject(ObjectOutputStream out)的方法进行序列化。
ObjectInputStream会调用Student对象的readObject(ObjectInputStream in)的方法进行反序列化。
方法三:若Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。
ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。
ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。
4)JDK类库中序列化的步骤
步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\objectfile.obj”));
步骤二:通过对象输出流的writeObject()方法写对象:
out.writeObject(“Hello”);
out.writeObject(new Date());
5)JDK类库中反序列化的步骤
步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\\objectfile.obj”));
步骤二:通过对象输出流的readObject()方法读取对象:
String obj1 = (String)in.readObject();
Date obj2 = (Date)in.readObject();
说明:为了正确读取数据,完成反序列化,必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。
为了更好地理解Java序列化与反序列化,选择方法一编码实现。
Student类定义如下:
- package com.jieke.io;
- import java.io.Serializable;
- /**
- *Title:学生类
- *Description:实现序列化接口的学生类
- *Copyright: copyright(c) 2012
- *Filename: Student.java
- *@author Wang Luqing
- *@version 1.0
- */
- public class Student implements Serializable
- {
- private String name;
- private char sex;
- private int year;
- private double gpa;
- public Student()
- {
- }
- public Student(String name,char sex,int year,double gpa)
- {
- this.name = name;
- this.sex = sex;
- this.year = year;
- this.gpa = gpa;
- }
- public void setName(String name)
- {
- this.name = name;
- }
- public void setSex(char sex)
- {
- this.sex = sex;
- }
- public void setYear(int year)
- {
- this.year = year;
- }
- public void setGpa(double gpa)
- {
- this.gpa = gpa;
- }
- public String getName()
- {
- return this.name;
- }
- public char getSex()
- {
- return this.sex;
- }
- public int getYear()
- {
- return this.year;
- }
- public double getGpa()
- {
- return this.gpa;
- }
- }
-
package com.jieke.io;
-
import java.io.Serializable;
-
-
/**
-
*Title:学生类
-
*Description:实现序列化接口的学生类
-
*Copyright: copyright(c) 2012
-
*Filename: Student.java
-
*@author Wang Luqing
-
*@version 1.0
-
*/
-
public class Student implements Serializable
-
{
-
private String name;
-
private char sex;
-
private int year;
-
private double gpa;
-
-
public Student()
-
{
-
-
}
-
public Student(String name,char sex,int year,double gpa)
-
{
-
this.name = name;
-
this.sex = sex;
-
this.year = year;
-
this.gpa = gpa;
-
}
-
-
public void setName(String name)
-
{
-
this.name = name;
-
}
-
-
public void setSex(char sex)
-
{
-
this.sex = sex;
-
}
-
-
public void setYear(int year)
-
{
-
this.year = year;
-
}
-
-
public void setGpa(double gpa)
-
{
-
this.gpa = gpa;
-
}
-
-
public String getName()
-
{
-
return this.name;
-
}
-
-
public char getSex()
-
{
-
return this.sex;
-
}
-
-
public int getYear()
-
{
-
return this.year;
-
}
-
-
public double getGpa()
-
{
-
return this.gpa;
-
}
-
}
把Student类的对象序列化到文件O:\\Java\\com\\jieke\\io\\student.txt,并从该文件中反序列化,向console显示结果。代码如下:
- import java.io.*;
- /**
- *Title:应用学生类
- *Description:实现学生类实例的序列化与反序列化
- *Copyright: copyright(c) 2012
- *Filename: UseStudent.java
- *@author Wang Luqing
- *@version 1.0
- */
- public class UseStudent
- {
- public static void main(String[] args)
- {
- Student st = new Student("Tom",'M',20,3.6);
- File file = new File("O:\\Java\\com\\jieke\\io\\student.txt");
- try
- {
- file.createNewFile();
- }
- catch(IOException e)
- {
- e.printStackTrace();
- }
- try
- {
- //Student对象序列化过程
- FileOutputStream fos = new FileOutputStream(file);
- ObjectOutputStream oos = new ObjectOutputStream(fos);
- oos.writeObject(st);
- oos.flush();
- oos.close();
- fos.close();
- //Student对象反序列化过程
- FileInputStream fis = new FileInputStream(file);
- ObjectInputStream ois = new ObjectInputStream(fis);
- Student st1 = (Student) ois.readObject();
- System.out.println("name = " + st1.getName());
- System.out.println("sex = " + st1.getSex());
- System.out.println("year = " + st1.getYear());
- System.out.println("gpa = " + st1.getGpa());
- ois.close();
- fis.close();
- }
- catch(ClassNotFoundException e)
- {
- e.printStackTrace();
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- }
- }
-
import java.io.*;
-
-
/**
-
*Title:应用学生类
-
*Description:实现学生类实例的序列化与反序列化
-
*Copyright: copyright(c) 2012
-
*Filename: UseStudent.java
-
*@author Wang Luqing
-
*@version 1.0
-
*/
-
-
public class UseStudent
-
{
-
public static void main(String[] args)
-
{
-
Student st = new Student("Tom",'M',20,3.6);
-
File file = new File("O:\\Java\\com\\jieke\\io\\student.txt");
-
try
-
{
-
file.createNewFile();
-
}
-
catch(IOException e)
-
{
-
e.printStackTrace();
-
}
-
try
-
{
-
//Student对象序列化过程
-
FileOutputStream fos = new FileOutputStream(file);
-
ObjectOutputStream oos = new ObjectOutputStream(fos);
-
oos.writeObject(st);
-
oos.flush();
-
oos.close();
-
fos.close();
-
-
//Student对象反序列化过程
-
FileInputStream fis = new FileInputStream(file);
-
ObjectInputStream ois = new ObjectInputStream(fis);
-
Student st1 = (Student) ois.readObject();
-
System.out.println("name = " + st1.getName());
-
System.out.println("sex = " + st1.getSex());
-
System.out.println("year = " + st1.getYear());
-
System.out.println("gpa = " + st1.getGpa());
-
ois.close();
-
fis.close();
-
}
-
catch(ClassNotFoundException e)
-
{
-
e.printStackTrace();
-
}
-
catch (IOException e)
-
{
-
e.printStackTrace();
-
}
-
}
-
}
结果如下所示:
name = Tom
sex = M
year = 20
gpa = 3.6
总结:
1)Java序列化就是把对象转换成字节序列,而Java反序列化就是把字节序列还原成Java对象。
2)采用Java序列化与反序列化技术,一是可以实现数据的持久化,在MVC模式中很是有用;二是可以对象数据的远程通信。
Java中实现序列化的两种方式 Serializable 接口和 Externalizable接口
对象的序列化就是将对象写入输出流中。
反序列化就是从输入流中将对象读取出来。
用来实现序列化的类都在java.io包中,我们常用的类或接口有:
ObjectOutputStream:提供序列化对象并把其写入流的方法
ObjectInputStream:读取流并反序列化对象
Serializable:一个对象想要被序列化,那么它的类就要实现 此接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。
Externalizable:他是Serializable接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的writeExternal()和readExternal()方法可以指定序列化哪些属性;
但是如果你只想隐藏一个属性,比如用户对象user的密码pwd,如果使用Externalizable,并除了pwd之外的每个属性都写在writeExternal()方法里,这样显得麻烦,可以使用Serializable接口,并在要隐藏的属性pwd前面加上transient就可以实现了。
方法一:
实现Serializable接口。
序列化的时候的一个关键字:transient(临时的)。它声明的变量实行序列化操作的时候不会写入到序列化文件中去。
例子:
package demo2; import java.io.Serializable; //实现Serializable接口才能被序列化 public class UserInfo implements Serializable{ private String userName; private String usePass; private transient int userAge;//使用transient关键字修饰的变量不会被序列化 public String getUserName() { return userName; } public UserInfo() { userAge=20; } public UserInfo(String userName, String usePass, int userAge) { super(); this.userName = userName; this.usePass = usePass; this.userAge = userAge; } public void setUserName(String userName) { this.userName = userName; } public String getUsePass() { return usePass; } public void setUsePass(String usePass) { this.usePass = usePass; } public int getUserAge() { return userAge; } public void setUserAge(int userAge) { this.userAge = userAge; } @Override public String toString() { return "UserInfo [userName=" + userName + ", usePass=" + usePass + ",userAge="+(userAge==0?"NOT SET":userAge)+"]"; } }
package demo2; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Date; public class UserInfoTest { /** * 序列化对象到文件 * @param fileName */ public static void serialize(String fileName){ try { ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName)); out.writeObject("序列化的日期是:");//序列化一个字符串到文件 out.writeObject(new Date());//序列化一个当前日期对象到文件 UserInfo userInfo=new UserInfo("郭大侠","961012",21); out.writeObject(userInfo);//序列化一个会员对象 out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 从文件中反序列化对象 * @param fileName */ public static void deserialize(String fileName){ try { ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName)); String str=(String) in.readObject();//刚才的字符串对象 Date date=(Date) in.readObject();//日期对象 UserInfo userInfo=(UserInfo) in.readObject();//会员对象 System.out.println(str); System.out.println(date); System.out.println(userInfo); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args){ // serialize("text"); deserialize("text");//这里userAge取读不到是因为使用了transient修饰,所以得到的是默认值 /** * 我修改了一下UserInfo的无参构造,在无参构造中给userAge属性赋值蛋反序列化得到的结果还是一样。 * 得出结论: * 当从磁盘中读出某个类的实例时,实际上并不会执行这个类的构造函数, * 而是载入了一个该类对象的持久化状态,并将这个状态赋值给该类的另一个对象。 */ } }
方法二:
实现Externalizable接口:
使用这个接口的场合是这样的:
一个类中我们只希望序列化一部分数据,其他数据都使用transient修饰的话显得有点麻烦,这时候我们使用externalizable接口,指定序列化的属性。
例子:
package demo2;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
//实现Externalizable接口序列化
public class UserInfo implements Externalizable{
private String userName;
private String usePass;
private int userAge;
public String getUserName() {
return userName;
}
public UserInfo() {
userAge=20;//这个是在第二次测试使用,判断反序列化是否通过构造器
}
public UserInfo(String userName, String usePass, int userAge) {
super();
this.userName = userName;
this.usePass = usePass;
this.userAge = userAge;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUsePass() {
return usePass;
}
public void setUsePass(String usePass) {
this.usePass = usePass;
}
public int getUserAge() {
return userAge;
}
public void setUserAge(int userAge) {
this.userAge = userAge;
}
@Override
public String toString() {
return "UserInfo [userName=" + userName + ", usePass=" + usePass + ",userAge="+(userAge==0?"NOT SET":userAge)+"]";
}
public void writeExternal(ObjectOutput out) throws IOException {
/*
* 指定序列化时候写入的属性。这里仍然不写入年龄
*/
out.writeObject(userName);
out.writeObject(usePass);
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
/*
* 指定反序列化的时候读取属性的顺序以及读取的属性
* 如果你写反了属性读取的顺序,你可以发现反序列化的读取的对象的指定的属性值也会与你写的读取方式一一对应。因为在文件中装载对象是有序的
*/
userName=(String) in.readObject();
usePass=(String) in.readObject();
}
}
测试:
package demo2;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
public class UserInfoTest {
/**
* 序列化对象到文件
* @param fileName
*/
public static void serialize(String fileName){
try {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));
out.writeObject("序列化的日期是:");//序列化一个字符串到文件
out.writeObject(new Date());//序列化一个当前日期对象到文件
UserInfo userInfo=new UserInfo("郭大侠","961012",21);
out.writeObject(userInfo);//序列化一个会员对象
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 从文件中反序列化对象
* @param fileName
*/
public static void deserialize(String fileName){
try {
ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));
String str=(String) in.readObject();//刚才的字符串对象
Date date=(Date) in.readObject();//日期对象
UserInfo userInfo=(UserInfo) in.readObject();//会员对象
System.out.println(str);
System.out.println(date);
System.out.println(userInfo);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
// serialize("text");
deserialize("text");
/**
* 我修改了一下UserInfo的无参构造,在无参构造中给userAge属性赋值蛋反序列化得到的结果是userAge变成了20。
* 得出结论:
* 当从磁盘中读出某个类的实例时,如果该实例使用的是Externalizable序列化,会执行这个类的构造函数,
* 然后调用readExternal给其他属性赋值
*/
}
}
原理分析:
总结:
首先,我们在序列化UserInfo对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列
一些api:
Externalizable 实例类的唯一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。 若某个要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替定制的 writeObject 和 readObject 方法实现。
writeExternal(ObjectOutput out)
该对象可实现 writeExternal 方法来保存其内容,它可以通过调用 DataOutput 的方法来保存其基本值,或调用 ObjectOutput 的 writeObject 方法来保存对象、字符串和数组。
readExternal(ObjectInput in)
对象实现 readExternal 方法来恢复其内容,它通过调用 DataInput 的方法来恢复其基础类型,调用 readObject 来恢复对象、字符串和数组。
externalizable和Serializable的区别:(静态属性持保留意见,60%偏向不能直接序列化)
1:
实现serializable接口是默认序列化所有属性,如果有不需要序列化的属性使用transient修饰。
externalizable接口是serializable的子类,实现这个接口需要重写writeExternal和readExternal方法,指定对象序列化的属性和从序列化文件中读取对象属性的行为。
2:
实现serializable接口的对象序列化文件进行反序列化不走构造方法,载入的是该类对象的一个持久化状态,再将这个状态赋值给该类的另一个变量
实现externalizable接口的对象序列化文件进行反序列化先走构造方法得到控对象,然后调用readExternal方法读取序列化文件中的内容给对应的属性赋值。
serialVersionUID作用:序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 有两种生成方式: 一个是默认的1L,比如:private static final long se...
serialVersionUID作用:
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。
有两种生成方式:
一个是默认的1L,比如:private static final long serialVersionUID = 1L;
一个是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段;
几个问题:
1、 如果一个类没有实现Serializable接口,但是它的基类实现 了,这个类可不可以序列化?
2、 和上面相反,如果一个类实现了Serializable接口,但是它的父类没有实现 ,这个类可不可以序列化?
第1个问题:一个类实现 了某接口,那么它的所有子类都间接实现了此接口,所以它可以被 序列化。
第2个问题:Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义 的状态能被正确 的保存以及读取吗?
第3个问题:如果将一个对象写入某文件(比如是a),那么之后对这个对象进行一些修改,然后把修改的对象再写入文件a,那么文件a中会包含该对象的两个 版本吗?
关于几个问题的答案见这个博客:http://blog.csdn.net/moreevan/article/details/6698529(我是从上面copy转过来的,不愿深入了)
Java将对象保存到文件中/从文件中读取对象
1.保存对象到文件中
Java语言只能将实现了Serializable接口的类的对象保存到文件中,利用如下方法即可:
public static void writeObjectToFile(Object obj) { File file =new File("test.dat"); FileOutputStream out; try { out = new FileOutputStream(file); ObjectOutputStream objOut=new ObjectOutputStream(out); objOut.writeObject(obj); objOut.flush(); objOut.close(); System.out.println("write object success!"); } catch (IOException e) { System.out.println("write object failed"); e.printStackTrace(); } }
参数obj一定要实现Serializable接口,否则会抛出java.io.NotSerializableException异常。另外,如果写入的对象是一个容器,例如List、Map,也要保证容器中的每个元素也都是实现 了Serializable接口。例如,如果按照如下方法声明一个Hashmap,并调用writeObjectToFile方法就会抛出异常。但是如果是Hashmap<String,String>就不会出问题,因为String类已经实现了Serializable接口。另外如果是自己创建的类,如果继承的基类没有实现Serializable,那么该类需要实现Serializable,否则也无法通过这种方法写入到文件中。
Object obj=new Object(); //failed,the object in map does not implement Serializable interface HashMap<String, Object> objMap=new HashMap<String,Object>(); objMap.put("test", obj); writeObjectToFile(objMap);
2.从文件中读取对象
可以利用如下方法从文件中读取对象
public static Object readObjectFromFile() { Object temp=null; File file =new File("test.dat"); FileInputStream in; try { in = new FileInputStream(file); ObjectInputStream objIn=new ObjectInputStream(in); temp=objIn.readObject(); objIn.close(); System.out.println("read object success!"); } catch (IOException e) { System.out.println("read object failed"); e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return temp; }
读取到对象后,再根据对象的实际类型进行转换即可。
java中类实现Serializable接口的原因
背景:一个java中的类只有实现了Serializable接口,它的对象才是可序列化的。如果要序列化某些类的对象,这些类就必须实现Serializable接口。Serializable是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。
为什么要进实现Serializable接口:为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来,这是java中的提供的保存对象状态的机制—序列化。
在什么情况下需要使用到Serializable接口呢?
1、当想把的内存中的对象状态保存到一个文件中或者数据库中时候;
2、当想用套接字在网络上传送对象的时候;
3、当想通过RMI传输对象的时候;
serialVersionUID
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:
a. 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
b. 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
代码实现:
在这里 定义一个实现了Serializable接口的Person类
import java.io.Serializable;
public class Person implements Serializable {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
再定义一个SerializationUtils类来模拟 序列化和反序列化的过程
import java.io.*;
public class SerializationUtils {
private static String FILE_NAME = "f:/obj";
//序列化 写的过程
public static void write(Serializable s){
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
objectOutputStream.writeObject(s);
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//反序列化 读的过程
public static Object read(){
Object obj=null;
// 反序列化
try {
ObjectInput input = new ObjectInputStream(new FileInputStream(FILE_NAME));
obj = input.readObject();
input.close();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
测试函数
import com.txp.SerializationUtils;
import org.junit.Test;
public class testSerializable {
@Test
public void testWrite(){
Person person=new Person();
person.setId(1);
person.setName("张丹");
SerializationUtils.write(person);
}
@Test
public void testRead(){
Person p = (Person) SerializationUtils.read();
System.out.println(p.getName());
}
}
先运行testWrite()实现序列化持久化,再运行testRead()实现反序列化读出数据 ,这一次的Person类中没有给定serialVersionUID
,结果会输出‘张丹’。
如果此时给Person类加一个属性 age,运行testRead(),会抛出会抛出 java.io.InvalidClassException异常。因为JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,如果相同,则认为类没有发生改变,可以把数据流load为实例对象;如果不相同,对不起,JVM会抛异常InvalidClassException
,这是JVM一个很好的一个校验机制,确保类的一致性。
但是如果显式给定serialVersionUID
(而隐式声明则是我不声明,编译器在编译的时候帮我生成。),即是 private static final long serialVersionUID = XXL;,修改Person类如下:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
再进行同样的操作过程,则不会抛出异常,会打印出结果。但是最好不要这样操作,要在类修改后,先序列化,再但序列化。确保类的前后一致性。
参考文章:
https://www.cnblogs.com/yoohot/p/6019767.html
https://blog.csdn.net/jaryle/article/details/52598296
http://www.cnblogs.com/DreamDrive/p/5412931.html