做为一个由影视圈转行作Java的菜鸟来讲,读书是很关键的,本系列是用来记录《编写高质量代码 改善java程序的151个建议》这本书的读书笔记。方便本身查看,也方便你们查阅。java
建议11:养成良好习惯,显示声明UID程序员
序列化Serializable是Java提供的通用数据保存和读取的接口。任何类只要实现了Serializable接口,就能够被保存到文件中,或者做为数据流经过网络发送到别的地方。服务器
package OSChina.Serializable; import java.io.Serializable; public class Man implements Serializable { private static final long serialVersionUID = 1L; private String username; private String password; public Man(String username, String password) { this.username = username; this.password = password; } 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; } }
package OSChina.Serializable; import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private Man man; private String username; private transient int age; public Person() { System.out.println("person constru"); } public Person(Man man, String username, int age) { this.man = man; this.username = username; this.age = age; } public Man getMan() { return man; } public void setMan(Man man) { this.man = man; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
package OSChina.Serializable; import java.io.*; public class MainTest { private static final String FILE_NAME = "D:/data/rtdata.txt"; public static void writeSerializableObject() { try { Man man = new Man("huhx", "123456"); Person person = new Person(man, "刘力", 21); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(FILE_NAME)); objectOutputStream.writeObject("string"); objectOutputStream.writeObject(person); objectOutputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // Serializable:反序列化对象 public static void readSerializableObject() { try { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(FILE_NAME)); String string = (String) objectInputStream.readObject(); Person person = (Person) objectInputStream.readObject(); objectInputStream.close(); System.out.println(string + ", age: " + person.getAge() + ", man username: " + person.getMan().getUsername()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { writeSerializableObject(); readSerializableObject(); } }
这是一个简单的JavaBean,实现了Serializable接口,能够在网络上传输,也能够在本地存储而后读取。网络
序列化和反序列化的类在不一致的状况下,反序列化时会报一个InalidClassException异常,缘由是序列化和反序列化所对应的类版本发生了变化,JVM不能把数据流转换为实例对象。刨根问底:JVM是根据什么来判断一个类的版本呢?app
经过SerializableUID,也叫作流标识符(Stream Unique Identifier),即类的版本定义的,它能够显示声明也能够隐式声明。显示声明格式以下:分布式
private static final long serialVersionUID = 1867341609628930239L;
serialVersionUID的做用:函数
JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,若是相同,则认为类没有改变,能够把数据load为实例相同;若是不相同,抛个异常InviladClassException。性能
刚开始生产者和消费者持有的Person类一致,都是V1.0,某天生产者的Person类变动了,增长了一个“年龄”属性,升级为V2.0,因为种种缘由(好比程序员疏忽,升级时间窗口不一样等)消费端的Person类仍是V1.0版本,添加的代码为 priavte int age;以及对应的setter和getter方法。this
此时虽然生产这和消费者对应的类版本不一样,可是显示声明的serialVersionUID相同,序列化也是能够运行的,所带来的业务问题就是消费端不能读取到新增的业务属性(age属性而已)。spa
经过此例,咱们反序列化也实现了版本向上兼容的功能,使用V1.0版本的应用访问了一个V2.0的对象,这无疑提升了代码的健壮性。
显示声明serialVersionUID能够避免对象的不一致,但尽可能不要以这种方式向JVM撒谎。
建议12:避免用序列化类在构造函数中为不变量赋值
咱们知道带有final标识的属性是不变量,也就是只能赋值一次,不能重复赋值,可是在序列化类中就有点复杂了,好比这个类:
在构造函数中不容许对final变量从新赋值。
去掉final以后呢?
public class Person implements Serializable { private static final long serialVersionUID = 1L; public static String perName="程咬金"; public Person() { System.out.println("person constru"); perName = "秦叔宝"; } }
public class Test { public static void main(String[] args) { System.out.println(perName); } }
序列化与反序列化的时候构造函数不会执行。
建议13:避免为final变量复杂赋值
如今的Java好像已经命令禁止final的从新赋值了!
建议14:使用序列化类的私有方法巧妙解决部分属性持久化问题
例如:一个计税系统和一个HR系统,计税系统须要从HR系统得到人员的姓名和基本工资,而HR系统的工资分为两部分:基本工资和绩效工资,绩效工资是保密的,不能泄露到外系统。
public class Salary implements Serializable { private static final long serialVersionUID = 2706085398747859680L; // 基本工资 private int basePay; // 绩效工资 private int bonus; public Salary(int _basepay, int _bonus) { this.basePay = _basepay; this.bonus = _bonus; } //Setter和Getter方法略 }
public class Person implements Serializable { private static final long serialVersionUID = 9146176880143026279L; private String name; private Salary salary; public Person(String _name, Salary _salary) { this.name = _name; this.salary = _salary; } //Setter和Getter方法略 }
public class Serialize { public static void main(String[] args) { // 基本工资1000元,绩效工资2500元 Salary salary = new Salary(1000, 2500); // 记录人员信息 Person person = new Person("张三", salary); // HR系统持久化,并传递到计税系统 SerializationUtils.writeObject(person); } }
public class Deserialize { public static void main(String[] args) { Person p = (Person) SerializationUtils.readObject(); StringBuffer buf = new StringBuffer(); buf.append("姓名: "+p.getName()); buf.append("\t基本工资: "+p.getSalary().getBasePay()); buf.append("\t绩效工资: "+p.getSalary().getBonus()); System.out.println(buf); } }
但这个不符合需求,你可能会想到一下四种解决方案:
一、java 的transient关键字为咱们提供了便利,你只须要实现Serilizable接口,将不须要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
static修饰的变量也不能序列化。
在bonus前加上关键字transient,使用transient关键字就标志着salary失去了分布式部署的功能,一旦出现性能问题,再想分布式部署就不可能了,此方案否认。
注:分布式部署是将数据分散的存储于多台独立的机器设备上,采用可扩展的系统结构,利用多台存储服务器分担存储负担,利用未知服务器定位存储信息,提升了系统的可靠性、可用性和扩展性。
二、新增业务对象:增长一个Person4Tax类,彻底为计税系统服务,就是说它只有两个属性:姓名和基本工资。符合开闭原则,并且对原系统也没有侵入性,只是增长了工做量而已。可是这个方法不是最优方法;
下面展现一个优秀的方案,其中实现了Serializable接口的类能够实现两个私有方法:writeObject和readObject,以影响和控制序列化和反序列化的过程。
public class Person implements Serializable { private static final long serialVersionUID = 9146176880143026279L; private String name; private transient Salary salary; public Person(String _name, Salary _salary) { this.name = _name; this.salary = _salary; } //序列化委托方法 private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeInt(salary.getBasePay()); } //反序列化委托方法 private void readObject(ObjectInputStream input)throws ClassNotFoundException, IOException { input.defaultReadObject(); salary = new Salary(input.readInt(), 0); } }
其它代码不作任何改动,运行以后结果为:
这里用到了序列化的独有机制:序列化回调。
Java调用ObjectOutputStream类把一个对象转换成数据流时,会经过反射(refection)检查被序列化的类是否有writeObject方法,而且检查其实否符合私有,无返回值的特性,如有,则会委托该方法进行对象序列化,若没有,则由ObjectOutputStream按照默认规则继续序列化。一样,从流数据恢复成实例对象时,也会检查是否有一个私有的readObject方法,若是有经过该方法读取属性值。
① oos.defaultWriteObject():告知JVM按照默认规则写入对象
② ois.defaultWriteObject():告知JVM按照默认规则读出对象
③ oos.writeXX和ois.readXX
分别是写入和对出响应的值,相似一个队列,先进先出,若是此处有复杂的数据逻辑,建议按封装Collection对象处理。
上面的方式也是Person失去了分布式部署的能了,确实是,可是HR系统的难点和重点是薪水的计算,特别是绩效工资,它所依赖的参数很复杂,计算公式也不简单(通常是引入脚本语言,个性化公式定制)而相对来讲Person类基本上都是静态属性,计算的可能性不大,因此即便为性能考虑,Person类为分布式部署的意义也不大。
既然这样,为什么不直接使用transient???
建议15:break万万不可忘