做爲一個由影視圈轉行作Java的菜鳥來講,讀書是很關鍵的,本系列是用來記錄《編寫高質量代碼 改善java程序的151個建議》這本書的讀書筆記。方便本身查看,也方便你們查閱。java
建議14:使用序列化類的私有方法巧妙解決部分屬性持久化問題編程
建議15:break萬萬不可忘數組
建議16:易變業務使用腳本語言編寫安全
建議17:慎用動態編譯服務器
建議18:淺談Java instanceofapp
建議19:斷言絕對不是雞肋框架
建議20:不要只替換一個類分佈式
建議14:使用序列化類的私有方法巧妙解決部分屬性持久化問題ide
例如:一個計稅系統和一個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萬萬不可忘
建議16:易變業務使用腳本語言編寫
好比PHP、Ruby、groovy、JavaScript等
建議17:慎用動態編譯
動態編譯一直是Java的夢想,從Java6開始支持動態編譯,能夠在運行期直接編譯.Java文件,執行.class文件,而且得到相關的輸入輸出,甚至還能監聽相關的事件。
一、概念
靜態編譯:一次性編譯,在編譯的時候把你全部的模塊都編譯進去。
動態編譯:按需編譯,程序在運行的時候,用到哪一個模塊就編譯哪一個模塊。
二、代碼實例
public class Ay{ public static void main(String[] args) throws Exception{ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); int flag = compiler.run(null, null, null,"D:\\HelloWorld.java"); System.out.println(flag == 0 ? "編譯成功" : "編譯失敗"); } } /** * D盤放置的類的內容 */ public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } }
解釋一下:
第一個參數:爲java編譯器提供參數
第二個參數:獲得java編譯器的輸出信息
第三個參數:接受編譯器的錯誤信息
第四個參數:可變參數(是一個String數組)能傳入一個或多個java源文件
返回值:0表示編譯成功,非0表示編譯失敗
三、動態運行編譯好的類
public class Ay{ public static void main(String[] args) throws Exception{ //得到系統的java編譯器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //編譯文件,編譯成功返回 0 不然 返回 1 int flag = compiler.run(null, null, null,"D:\\HelloWorld.java"); System.out.println(flag == 0 ? "編譯成功" : "編譯失敗"); //指定class路徑,默認和源代碼路徑一致,加載class URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:/d:/")}); Object printer = classLoader.loadClass("HelloWorld").newInstance(); System.out.println(printer.toString()); } }
運行結果:
編譯成功 HelloWorld@4c583ecf
四、慎用動態編譯
① 在框架中謹慎使用
好比要在struts中使用動態編譯,動態實現一個類,它若繼承自ActionSupport就但願它成爲一個Action。能作到,可是debug很困難;再好比在Spring中,寫一個動態類,要讓它注入到Spring容器中,這是須要花費老大功夫的。
② 不要在要求高性能的項目中使用
動態編譯畢竟須要一個編譯過程,與靜態編譯相比多了一個執行環節,所以在高性能的項目中不要使用動態編譯
③ 動態編譯要考慮安全問題
它是很是典型的注入漏洞,只要上傳一個惡意Java程序就可讓你全部的安全工做毀於一旦。
④ 記錄動態編譯過程
建議記錄源文件、目標文件、編譯過程、執行過程等日誌。不只僅爲了診斷,仍是爲了安全和審計,對Java項目來講,動態編譯和運行時很不讓人放心的,留下這些依據能夠更好地優化程序。
建議18:淺談Java instanceof
一、instanceof是Java中的二元運算符,左邊是對象,右邊是類;當對象是右邊類或子類所建立的對象時,返回true,否者,返回false。
注:
① 類的實例包含自己的實例,以及全部直接或間接子類的實例
② instanceof左邊顯示聲明的類型與右邊操做元必須是同種類或存在繼承關係,也就是說須要位於同一個繼承樹,否者會編譯報錯
二、instanceof用法
① 左邊的對象實例不能是基本數據類型
② 左邊的對象和右邊的類不在同一個繼承樹上
③ null用instanceof跟任何類型比較時都是false
建議19:斷言絕對不是雞肋
一、簡介
斷言也就是所謂的assert,是jdk1.4中加入的新功能。
他主要使用在代碼開發和測試階段,用於對某些關鍵數據的判斷,若是這個關鍵數據不是你程序所預期的數據,程序就提出警告或退出。
當軟件正式發佈後,能夠取消斷言部分的代碼。
二、語法:
assert<布爾表達式>
assert<布爾表達式> : <錯誤信息>
在布爾表達式爲假時,跑出AssertionError錯誤,並附帶了錯誤信息。
assert的語法比較簡單,有如下兩個特性:
① assert默認是不開啓的
② assert跑出的異常AssertionError繼承自Error
斷言失敗後,JVM會跑出一個AssertionError的錯誤,它繼承自Error,這是一個錯誤,不可恢復。
三、assert的使用禁忌
① 對外的公開方法中不可以使用
防護式編程最核心的部分就是:全部的外部因素(輸入參數、環境變量、上下文)都是「邪惡」的,都存在着企圖摧毀程序的罪惡本源,爲了抵制它,咱們要在程序到處設置合法性檢查,不知足條件就不執行後續程序,以保護後續程序的正確性,但此時不能用斷言作輸入檢查,特別是公開方法。
② 在執行邏輯代碼的狀況下
assert的支持是可選的,在開發時運行,生產環境下中止運行便可,所以在assert的布爾表達式中不能執行邏輯代碼,否者會由於環境的不一樣產生不一樣的邏輯。
public void doSomething(List list, Object element) { assert list.remove(element) : "刪除元素" + element + "失敗"; /*業務處理*/ }
這段代碼在assert啓用的環境下,沒有任何問題,可是一旦投入生產環境,就不會啓用斷言了,這個方法就完全完蛋,list的刪除動做永遠不會執行,永遠不會報錯或異常,由於根本沒有執行!
四、assert的使用場景
按照正常的執行邏輯不可能到達的代碼區域可使用assert。
① 在私有方法中放置assert做爲輸入參數的校驗
私有方法的使用者是本身,是本身能夠控制的,所以加上assert能夠更好地預防本身犯錯或者無心的程序犯錯。
② 流程控制中不可能到達的區域
程序執行到assert這裏就是錯誤的
③ 創建程序探針
咱們可能會在一段程序中定義兩個變量,分別代兩個不一樣的業務含義,可是二者有固定的關係,例如:var1=var2 * 2,那咱們就能夠在程序中處處設"樁"了,斷言這二者的關係,若是不知足即代表程序已經出現了異常,業務也就沒有必要運行下去了。
建議20:不要只替換一個類
注意:發佈應用系統時禁止使用類文件替換方式,總體WAR包發佈纔是萬全之策。