编写高质量代码:改善Java的151个建议二:11-14(序列化相关内容)
该书籍PDF下载地址:http://download.csdn.net/download/muyeju/10001473
保存在磁盘上的对象文件包括2个部分:
(1):类文件描述信息:包括包路径、继承关系、访问权限、变量描述、变量访问权限、方法签名、返回值、以及变量的关联类信息。
(2):非瞬太(transient关键字)和非静态(static关键字)的实例变量值
11.养成良好习惯,显示声明UID
当一个继承了Serializable接口,这个类就可以被序列化。继承Serializable的目的是为了可持久化,如果网络传输或本地存储,为系统的分布和异构部署提供先决支持条件,如果没有序列化,那么远程调用、对象数据库都不存在。
序列化:就是将一个对象从内存块转换为可传输的数据流
反序列化:将对象的数据流转换为一个实例对象
问题:当序列化和反序列化所对于的版本不一致就会抛异常InvalidClassException,JVM不能将数据流转为实例对象。JVM是通过serialVersionUID来判断序列化和反序列化所对于的版本是否一致的,所以就需要声明serialVersionUID
建议显示声明seriavalVersionUID,级,将鼠标指向该类,然后选则Add default serial version ID或者Add generated serial Version ID
Add default serial version ID:其值是private static final long serialVersionUID = 1L;当对类进行了不兼容性修改时,需要修改UID,建议选择这种
Add generated serial Version ID:如果修改了属性,不重新生成UID时,默认值是不会变的,也可以正常反序列化,但不推荐,毕竟UID的值与实际不符。
隐士声明UID:
12.在序列化的类中,不使用构造函数为final变量赋值
序列化修饰的类中,如果final修饰的属性是一个直接量(不变量),在反序列化时就会重新计算其值。
例:下列代码name如果重新修改了值,然后在反序列化,那么结果就是修改后的值
public class TestClass implements Serializable {
private static final long serialVersionUID = 1L;
private final String name = "张三" ;
}
但是,如果是通过构造方法来赋值,那么修改后的值是不会变的。因为反序列化时构造函数不会执行。
例:如果修改了name的值,其值是不会改变的
public class TestClass implements Serializable {
private static final long serialVersionUID = 1L;
private final String name ;
public TestClass(){
this.name = "张三" ;
}
}
反序列化的过程:JVM从数据流中获取一个Object对象,然后根据数据流中的类文件描述信息,发现name是final变量,需要重新技术其值,于是就引用TestClass中的name值,但是此时JVM发现name没有赋值,不能引用,于是它就不在初始化,保存原值状态,所以name的值就不变
规则:
保持新旧对象的final变量相同,是序列化的基本规则之一
反序列化时构造函数不会执行
13.反序列化时,final在以下情况不会被重新赋值
(1):通过构造函数为final变量赋值
(2):通过方法返回值为final变量赋值
(3):final修饰的属性不是基本类型
14.使用序列化的私有方法,巧妙解决部分属性持久化方法,private void writeObject(ObjectOutputStream out)和private void readObject(ObjectInputStream in)
序列化机制:序列化回调。Java调用ObjectOutputStream类把一个对象转换成数据流时,会通过反射检查被序列化的类中是否有writeObject方法,且方法是私有、无返回的
如果有,就委托该方法进行序列化,没有,则由ObjectOutputSream按照默认规则继续序列化。同样,在由流数据恢复成实例对象时,也会检查是否有一个私有 的readObject()方法,有就会通过该方法读取属性值。
public class TestClass implements Serializable { private static final long serialVersionUID = 1L; private String name ; private transient Salary salary ; public TestClass(String name,Salary salary){ this.name = name ; this.salary = salary ; } private void writeObject(ObjectOutputStream out) throws IOException{ out.defaultWriteObject(); out.writeObject(salary.getBase()); } private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{ in.defaultReadObject(); salary = new Salary(in.readDouble(),0.0) ; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Salary getSalary() { return salary; } public void setSalary(Salary salary) { this.salary = salary; } }