做爲一個由影視圈轉行作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萬萬不可忘