做爲一個由影視圈轉行作Java的菜鳥來講,讀書是很關鍵的,本系列是用來記錄《編寫高質量代碼 改善java程序的151個建議》這本書的讀書筆記。方便本身查看,也方便你們查閱。java
建議1:不要在常量和變量中出現易混淆的字母程序員
建議2:莫讓常量蛻變成變量算法
建議3:三元操做符的類型務必同樣網絡
建議4:避免帶有變長參數的方法重載dom
建議5:別讓null值和空值威脅到變長方法 函數
建議6:覆寫邊長方法也循規蹈矩工具
建議7:警戒自增的陷阱this
建議8:不要讓舊語法困擾你spa
建議9:少用靜態導入.net
建議10:不要在本類中覆蓋靜態導入的變量和方法
建議11:養成良好習慣,顯示聲明UID
建議12:避免用序列化類在構造函數中爲不變量賦值
建議13:避免爲final變量複雜賦值
建議1:不要在常量和變量中出現易混淆的字母
建議2:莫讓常量蛻變成變量
常量蛻變成變量?你胡扯吧,加了final和static的常量怎麼可能會變呢?不可能爲此賦值的呀。真的不可能嗎?看看以下代碼:
import java.util.Random; public class Demo01 { public static void main(String[] args) { test02(); } public static void test02() { System.out.println("常量會變哦:" + Constant.RAND_CONST); } } interface Constant { public static final int RAND_CONST = new Random().nextInt(); }
RAND_CONST是常量嗎?它的值會變嗎?絕對會變!這種常量的定義方式是絕對不可取的,常量就是常量,在編譯期就必須肯定其值,不該該在運行期更改,不然程序的可讀性會很是差,甚至連做者本身都不能肯定在運行期發生了何種神奇的事情。
甭想着使用常量會變的這個功能來實現序列號算法、隨機種子生成,除非這真的是項目中的惟一方案,不然就放棄吧,常量仍是當常量使用。
注意:務必讓常量的值在運行期保持不變。
建議3:三元操做符的類型務必同樣
建議4:避免帶有變長參數的方法重載
建議5:別讓null值和空值威脅到變長方法
client.methodA("china", null);
編譯不經過,提示相同:方法模糊不清,編譯器不知道調用哪個方法,但這兩處代碼反應的味道是不一樣的。
String str = null;
client.methodA("china", str);
讓編譯器知道這個null值是String類型的,編譯便可順利經過,也就減小了錯誤的發生。
建議6:覆寫邊長方法也循規蹈矩
注意:覆寫的方法參數與父類相同,不只僅是類型、數量,還包括顯示形式.
建議7:警戒自增的陷阱
建議8:不要讓舊語法困擾你
建議9:少用靜態導入
從Java5開始引入靜態導入語法(import static),其目的是爲了減小字符的輸入量,提升代碼的可閱讀性,以便更好地理解程序。
對靜態導入,追尋兩個原則:
一、不使用通配符,除非是導入靜態常量類;
二、方法名具備明確、清晰表象意義的工具類。
建議10:不要在本類中覆蓋靜態導入的變量和方法
若是在一個類中的方法及屬性與靜態導入的方法及屬性相同會出現什麼問題呢?看下面的代碼
import static java.lang.Math.PI; import static java.lang.Math.abs; public class Client10 { // 常量名於靜態導入的PI相同 public final static String PI = "江疏影"; //方法名於靜態導入的方法相同 public static int abs(int abs) { return 0; } public static void main(String[] args) { System.out.println("PI = "+PI); System.out.println("abs(-100) = "+abs(-100)); } }
以上代碼中定義了一個String類型的常量PI,又定義了一個abs方法,與靜態導入的相同。首先說好消息,代碼沒有報錯,接下來是壞消息:咱們不知道那個屬性和方法別調用了,由於常量名和方法名相同,到底調用了那一個方法呢?運行以後結果爲:
很明顯是本地的方法被調用了,爲什麼不調用Math類中的屬性和方法呢?那是由於編譯器有一個「最短路徑」原則:若是可以在本類中查找到相關的變量、常量、方法,就不會去其它包或父類、接口中查找,以確保本類中的屬性、方法優先。
所以,若是要變動一個被靜態導入的方法,最好的辦法是在原始類中重構,而不是在本類中覆蓋。
建議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是根據什麼來判斷一個類的版本呢?
經過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方法。
此時雖然生產這和消費者對應的類版本不一樣,可是顯示聲明的serialVersionUID相同,序列化也是能夠運行的,所帶來的業務問題就是消費端不能讀取到新增的業務屬性(age屬性而已)。
經過此例,咱們反序列化也實現了版本向上兼容的功能,使用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的從新賦值了!