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中会包含该对象的两个 版本吗?
Java中实现序列化的两种方式 Serializable 接口和 Externalizable接口的更多相关文章
- Java中HashMap遍历的两种方式
Java中HashMap遍历的两种方式 转]Java中HashMap遍历的两种方式原文地址: http://www.javaweb.cc/language/java/032291.shtml 第一种: ...
- java中数组复制的两种方式
在java中数组复制有两种方式: 一:System.arraycopy(原数组,开始copy的下标,存放copy内容的数组,开始存放的下标,需要copy的长度); 这个方法需要先创建一个空的存放cop ...
- Java中对象拷贝的两种方式
引用的拷贝 //引用拷贝 private static void copyReferenceObject(){ Person p = new Person(23, "zhang") ...
- K:java中序列化的两种方式—Serializable或Externalizable
在java中,对一个对象进行序列化操作,其有如下两种方式: 第一种: 通过实现java.io.Serializable接口,该接口是一个标志接口,其没有任何抽象方法需要进行重写,实现了Serializ ...
- java中设置代理的两种方式
1 前言 有时候我们的程序中要提供可以使用代理访问网络,代理的方式包括http.https.ftp.socks代理.比如在IE浏览器设置代理. 那我们在我们的java程序中使用代理呢,有如下两种方式. ...
- java中实现同步的两种方式:syschronized和lock的区别和联系
Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...
- Java中实现多线程的两种方式之间的区别
Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线 ...
- Java中创建String的两种方式
1.在Java中,创建一个字符串有两种方式 String x = "abc";String y = new String("abc"); 这两种方式有什么区别呢 ...
- java中创建字符串的两种方式(“”与new String())及区别
结论:通过""创建的字符串实际上在java堆中只有一个,而通过new string创建出来的字符串在java堆中占有不同的内存. 第一个True表明这两个在内存中拥有相同的地址,那 ...
随机推荐
- fanqiang_bak
- Android actionbar 笔记
ActionBar是一种新増的导航栏功能,在Android 3.0之后加入到系统的API当中,它标识了用户当前操作界面的位置,并提供了额外的用户动作.界面导航等功能. 参考链接 https://dev ...
- zebra/quagga
参考:http://blog.chinaunix.net/uid-25513153-id-212328.html 一.zebra安装 .编译安装 vim ./lib/zebra.h + 增加: #if ...
- linux -- Ubuntu 安装搜狗输入法
在Ubuntu Kylin系统中,默认安装搜狗拼音输入法,但是在原生Ubuntu系统中则不是.这可以理解,毕竟搜狗输入法的Linux版有Kylin团队的不小功劳.由于搜狗输入法确实比Linux系统下其 ...
- Ironic , Openstack Baremetal Hypervisor
Ironic , Openstack Baremetal Hypervisor,首发于UnitedStack Inc.. 转自: http://ju.outofmemory.cn/entry/4876 ...
- C++字符串类型和数字之间的转换
转载:http://www.cnblogs.com/luxiaoxun/archive/2012/08/03/2621803.html 1.字符串数字之间的转换 字符串---字符数组(1)string ...
- 感谢各位亲们的大力支持,免费的HTML5学习课程《HTML5网页开发实例具体解释》连载已经结束了!
感谢各位亲们的大力支持,免费的HTML5学习课程<HTML5网页开发实例具体解释>连载已经结束了. 有兴趣的读者能够看我的博客,也能够看以下的链接逐个学习: 当里个当.免费的HTML5连 ...
- c++ __int64
C++的64位整数[原]by 赤兔 在做ACM题时,经常都会遇到一些比较大的整数.而常用的内置整数类型常常显得太小了:其中long 和 int 范围是[-2^31,2^31),即-2147483648 ...
- 阿里巴巴CI/CD之分层自动化
一佛是阿里巴巴B2B事业群高级产品经理.从事多年互联网系统的研发和测试工作,目前主要负责云效分层自动化测试的产品设计.因为自动化测试在实践过程中,总是碰到各种各样的问题,导致进入自动化测试盲区.所以, ...
- js正则表达式的应用
JavaScript表单验证email,判断一个输入量是否为邮箱email,通过正则表达式实现. //检查email邮箱 function isEmail(str){ var reg = /^([a- ...