Java中实现序列化的两种方式 Serializable 接口和 Externalizable接口

Posted guoDaXia的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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中实现序列化的两种方式 Serializable 接口和 Externalizable接口的主要内容,如果未能解决你的问题,请参考以下文章

Java中实现多线程的两种方式

Java中实现多线程的两种方式之间的区别

Spring Boot中实现定时任务的两种方式

java集合进行排序的两种方式

转:Java并发编程之十五:并发编程中实现内存可见的两种方法比较:加锁和volatile变量

java类实现序列化的方法