序列化和反序列化serialVersionUID中作用

一  名词定义

      序列化:将对象转化成二进制序列

      反序列化:将序列化的二进制序列转化为对象

       serialVersionUID:唯一的版本序列ID

二  序列化方式

     1. 实现序列化接口Serializable的方式

    eg.

    package com.shop.domain;

import java.util.Date;

public class Article implements java.io.Serializable {

        private static final long serialVersionUID = 1L;

        private Integer id;

        private String title; //文章标题

        private String content; // 文章内容

        private String faceIcon;//表情图标

        private Date postTime; //文章发表的时间

        private String ipAddr; //用户的ip

        private User author; //回复的用户

        public Integer getId() {

            return id;

        }

        public void setId(Integer id) {

            this.id = id;

        }

        public String getTitle() {

            return title;

        }

        public void setTitle(String title) {

            this.title = title;

        }

        public String getContent() {

            return content;

        }

        public void setContent(String content) {

            this.content = content;

        }

        public String getFaceIcon() {

            return faceIcon;

        }

        public void setFaceIcon(String faceIcon) {

            this.faceIcon = faceIcon;

        }

        public Date getPostTime() {

            return postTime;

        }

        public void setPostTime(Date postTime) {

            this.postTime = postTime;

        }

        public User getAuthor() {

            return author;

        }

        public void setAuthor(User author) {

            this.author = author;

        }

        public String getIpAddr() {

            return ipAddr;

        }

        public void setIpAddr(String ipAddr) {

            this.ipAddr = ipAddr;

        }

    }

2.实现序列化的第二种方式为实现接口Externalizable

eg.

* @see java.io.ObjectInput

* @see java.io.Serializable

* @since JDK1.1

*/

public interface Externalizable extends java.io.Serializable {

/**

* The object implements the writeExternal method to save its contents

* by calling the methods of DataOutput for its primitive values or

首先,我们在序列化对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,

package com.xiaohao.test;

import java.io.Externalizable;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInput;

import java.io.ObjectInputStream;

import java.io.ObjectOutput;i

mport java.io.ObjectOutputStream;

import java.text.SimpleDateFormat;

import java.util.Date;

/**

* 测试实体类

*/

class Person implements Externalizable{

private static final long serialVersionUID = 1L;

String userName; String password; String age;

public Person(String userName, String password, String age) {

super();

this.userName = userName;

this.password = password;

this.age = age;

}

public Person() {

super();

}

public String getAge() {

return age;

}

public void setAge(String age) {

this.age = age;

}

public String getUserName() {

return userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

/**

* 序列化操作的扩展类

*/

@Override

public void writeExternal(ObjectOutput out) throws IOException {

//增加一个新的对象

Date date=new Date();

out.writeObject(userName);

out.writeObject(password);

out.writeObject(date);

}

/**

* 反序列化的扩展类

*/

@Override

public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {

//注意这里的接受顺序是有限制的哦,否则的话会出错的

// 例如上面先write的是A对象的话,那么下面先接受的也一定是A对象...

userName=(String) in.readObject();

password=(String) in.readObject();

SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");

Date date=(Date)in.readObject();

System.out.println("反序列化后的日期为:"+sdf.format(date));

}

@Override

public String toString() {

//注意这里的年龄是不会被序列化的,所以在反序列化的时候是读取不到数据的

return "用户名:"+userName+"密 码:"+password+"年龄:"+age;

}

}

/**

* 序列化和反序列化的相关操作类

*/

class Operate{

/**

* 序列化方法

* @throws IOException

* @throws FileNotFoundException

*/

public void serializable(Person person) throws FileNotFoundException, IOException{

ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("a.txt"));

outputStream.writeObject(person);

}

/**

* 反序列化的方法

* @throws IOException

* @throws FileNotFoundException

* @throws ClassNotFoundException

*/

public Person deSerializable() throws FileNotFoundException, IOException, ClassNotFoundException{

ObjectInputStream ois=new ObjectInputStream(new FileInputStream("a.txt"));

return (Person) ois.readObject();

}

}

/**

* 测试实体主类

*/

public class Test{

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {

Operate operate=new Operate();

Person person=new Person("小浩","123456","20");

System.out.println("为序列化之前的相关数据如下:\n"+person.toString());

operate.serializable(person);

Person newPerson=operate.deSerializable();

System.out.println("-------------------------------------------------------");

System.out.println("序列化之后的相关数据如下:\n"+newPerson.toString()); }

}

首先,我们在序列化UserInfo对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了

哪些属性可以序列化,哪些不可以序列化,

所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动

调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,

就完成了反序列化。

注意:

1.java中的序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列化

2.如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,不要先接收对象B,

那样会报错.尤其在使用上面的Externalizable的时候一定要注意读取的先后顺序。

三  serialVersionUID作用

     

 serialVersionUID 是在序列化和反序列化的时候做比对使用;之又当序列化的版本号和反序列化的版本号匹配反序列化才会成功。否则咋反序列化的时候会报InvalidClassException异常。

   显示指定:在需要序列化的类中通过private static final long serialVersionUID = 123456789L;                        

                     方式手动指定 。在序列化的时候会使用指定的serialVersionUID,在序列化完成之后,即使修改过

                     序列化类,在原序列化对象反序列化的时候也不会nvalidClassException                

   隐式生成:如过序列化的类没有指定serialVersionUID Java序列化机制会根据编译的Class自动生成一个serialVersionUID。

                     当序列化完成之后,修改过源序列化类之后,在原序列化对象反序列化的时候会报nvalidClassException。

如下图:


拓展一:

静态变量的序列化和反序列化:

demo:

public class Test implements Serializable {

	private static final long serialVersionUID = 1L;

	public static int staticVar = 5;

	public static void main(String[] args) {
		try {
			//初始时staticVar为5
			ObjectOutputStream out = new ObjectOutputStream(
					new FileOutputStream("result.obj"));
			out.writeObject(new Test());
			out.close();

			//序列化后修改为10
			Test.staticVar = 10;

			ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
					"result.obj"));
			Test t = (Test) oin.readObject();
			oin.close();
			
			//再读取,通过t.staticVar打印新的值
			System.out.println(t.staticVar);
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

输出的结果是10. 序列化是对象的状态,静态变量是属于类的状态。所以反序列化获取到的对象里面没有静态属性,在序列化之后给静态属性重新赋值。

拓展二:Transient变量

情境1:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。  

解决:因为父类没有实现Serializable接口,所以父类不能被序列化。在反序列化的时候会先调用父类默认的无参数构造函数,

          如果父类的无参数构造函数中没有手动给变量赋值,在反序列化的时候父类对象的属性都会给根据数据类型给默认值。

          所以反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。

         

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值。

特性使用案例

我们熟悉使用 Transient 关键字可以使得字段不被序列化,那么还有别的方法吗?

根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化,

形成类图如图 2 

图 2. 案例程序类图

图 2. 案例程序类图

上图中可以看出,attr1、attr2、attr3、attr5 都不会被序列化,放在父类中的好处在于当有另外一个 Child 类时,attr1、attr2、attr3 依然不会被序列化,不用重复抒写 transient,代码简洁


猜你喜欢

转载自blog.csdn.net/ygy901006/article/details/79798285