題記:
花了一周把Peter Haggar的《practical Java》看了遍,有所感悟,年紀大了,
寫下筆記,方便日后查看.也希望有緣之人可以看看,做個渺小的指路人。
不足之處還望指正。
概述:
全書分為六個部分,包括一般技術、對象與相等性、異常處理、性能、多線程、對象。
一般技術:舉例了幾個java常見錯誤用法的說明和解釋,諸如array和vector的選擇,多態與instanceof等等
對象和相等性則:針對equals的詳細說明,是迄今本人見過對equals理解最深的一本書了,其中不乏java的一些規范
異常處理:主要介紹了java異常機制的使用細節,其中有一點就是return后的邏輯一律不執行在try finally模式里頭是無效的
性能:介紹了java常用的一些優化細節,諸如使用棧變量來代替堆變量,減少同步化,使用arraycopy方法來代替自己的循環復制數組等等
多線程:簡要的說明了java線程使用中一些常見的知識點,如果對java多線程有興趣的,可以看看《Java並發編程實戰》
對象:介紹了接口與繼承的關系與使用,對深入學習java框架源碼有一定的幫助,感興趣的可以多思考下其中的奧義
正題:
正題中將挑選自己覺得比較有用的一些知識點進行說明,但並不代表其他知識點就不重要(因人而異,盡信書 不如無書)
最后我會將代碼工程打包好,感興趣的可以去下載下來看看。
1.一般技術
實踐1:參數以by value 方式而非by reference 方式傳遞

1 /** 2 * java有值(基礎類型變量value)傳遞和引用(reference)傳遞 3 * 兩者的區別是引用將會隨着調用方法體的邏輯而發生改變 4 * 一個非常基礎的java知識點 5 * 獻給千千萬萬徘徊在java門口的求學者 6 * @author lwx 7 * TODO 8 * 參考: 9 * 2014-5-13 上午9:28:38 10 */ 11 public class Lesson1 { 12 13 public static void valueTest(int value){ 14 15 value=value+5; 16 System.out.println("valueTest-->"+value); 17 } 18 19 20 public static void referenceTest(StringBuffer obj){ 21 22 obj.append("123"); 23 24 System.out.println("referenceTest-->"+obj.toString()); 25 } 26 27 public static void main(String[] args) { 28 int value=2; 29 30 valueTest(value); 31 System.out.println(value);//2 32 StringBuffer obj =new StringBuffer("0"); 33 referenceTest(obj); 34 System.out.println(obj.toString());//0123 35 36 } 37 }
實踐2、3:final的用法 略
實踐4:在arrays和vectors之間慎重選擇
數組在java中使用率遠遠超過vector,因此這里就要明白何時應該使用vector
首先vector是線程安全的,這樣就保證了他可以同步控制對象,
其次vector內部實現也是數組,只不過是泛型對象的數組(數組更多時候存儲的是基礎類型)
最后就是數組的大小是無法自行拓展的,而vector是可以通過System.arraycopy()方法進行復制擴展vector的容量
實踐5:多態優於instanceof
這里必須明白java多態和instanceof用法,簡單說下兩者的概念
所謂的多態就是不同對象對同一個消息作出不同的響應,java中的多態包含了重載和重寫(覆蓋)
instanceof 運算符是用來在運行時指出對象是否是特定類的一個實例,通過返回一個布爾值來指出,這個對象是否是這個特定類或者是它的子類的一個
先看下面一個例子
1 /** 2 * 盡量使用多態而非instanceof 3 * @author lwx 4 * TODO 5 * 參考: 6 * 2014-5-13 上午10:39:41 7 */ 8 public class Lesson5 { 9 public static void main(String[] args) { 10 Employee mgr=new Manager(); 11 Employee pgr=new Programmer(); 12 System.out.println("經理的工資-->"+calcSalary(mgr)); 13 System.out.println("程序員的工資-->"+calcSalary(pgr)); 14 } 15 16 public static int calcSalary(Employee e){ 17 int salary=e.salary(); 18 if(e instanceof Programmer) 19 salary+=((Programmer)e).bonus(); 20 return salary; 21 } 22 } 23 interface Employee{ 24 public int salary(); 25 } 26 class Manager implements Employee{ 27 private static final int mgrSal=10000; 28 @Override 29 public int salary() { 30 // TODO Auto-generated method stub 31 return mgrSal; 32 } 33 } 34 class Programmer implements Employee{ 35 private static final int pgrSal=6500; 36 private static final int pgrBonus=1000; 37 @Override 38 public int salary() { 39 // TODO Auto-generated method stub 40 return pgrSal; 41 } 42 //程序員除了工資 還有項目獎金哦 那個公司有 求收留 43 public int bonus() { 44 // TODO Auto-generated method stub 45 return pgrBonus; 46 } 47 }
上面例子中 程序員在計算工資的時候是需要考慮獎金的,因此通過Instanceof來判定傳給
calcSalary方法的參數是否是Programmer類,如果是 則在原有工資計算方法上加上bonus()方法
表面上看,這樣的邏輯沒有問題,但是我們是需要考慮拓展的,加入現在還有產品經理,他的工資也有獎金 另外還有其他福利,在加上其他崗位 那么每增加一個崗位的變動
我們都需要去修改calcSalary方法,而這樣的設計明顯是不符合java的規范的
書本作者給出的方案是讓經理也有獎金的方法,這不過這個獎金是0 從而避免了instanceof的產生,具體做法看書本,此處略
實踐6:必要時才需要instanceof
java支持父類向下轉型,即使是錯誤的向下轉型 在編譯的時候是不會報錯的,因此容易讓開發人員帶來干擾
1 /** 2 * 必要時才用instanceof 3 * 必要的時候指的是 你需要父類向下轉子類 4 * @author lwx 5 * TODO 6 * 參考: 7 * 2014-5-13 上午11:18:32 8 */ 9 public class Lesson6 { 10 11 public static void main(String[] args) { 12 13 Shape circle=new Lesson6.Circle(); 14 Object triangle=new Lesson6.Triangle(); 15 16 //Lesson6.Triangle tri1=(Lesson6.Triangle )circle;//編譯通過 但是執行會報錯 java.lang.ClassCastException 17 if(circle instanceof Lesson6.Triangle){ 18 19 Lesson6.Triangle tri1=(Lesson6.Triangle )circle; 20 } 21 Lesson6.Triangle tri2=(Lesson6.Triangle )triangle; 22 } 23 24 static class Shape{} 25 static class Circle extends Shape{} 26 static class Triangle extends Shape{} 27 28 }
實踐7:一旦不再需要object reference,就將它設為null
接觸java的都明白java自帶的虛擬機有垃圾回收機制,不願太操心內存問題,其實作為一名合格的javaer也是需要考慮內存泄露的
況且java確實有存在,當然這里不再我們的討論話題中,為什么沒用的引用盡量要手動的去觸發unusefulObj=null呢
其實就是減輕JVM的工作量,gc不是隨時觸發的 這個應該要懂得
實例中的例子已經很不錯了

1 /** 2 * 手動去設置無用的引用為null 3 * @author lwx 4 * TODO 5 * 參考: 6 * 2014-5-13 下午1:56:03 7 */ 8 public class Lesson7 { 9 10 11 12 public static void main(String[] args) { 13 14 //testGC();//GC測試CPU性能 15 16 Customers customers=new Customers(""); 17 18 //執行一堆邏輯 此處省略 19 20 /* 21 1. 22 無用時候釋放對象 23 customers=null;// 24 */ 25 26 27 /*2. 28 * 上面的情況 存在一個問題 假如customers我們還需要 29 * 只是他的數據我們不需要引用了 或則customers生命周期跟系統應用一個周期 30 * 那么我們就只需要釋放custIdArray內存就可以達到效果了 31 * */ 32 //由於我們沒有辦法直接接觸 因此需要開放方法給我們去觸發 加入一個unrefCust()方法 33 customers.unrefCust();// 34 35 } 36 37 private static void testGC() { 38 Runtime rt=Runtime.getRuntime(); 39 long mem=rt.freeMemory(); 40 System.out.println("空閑CUP==>"+mem/1024/1024); 41 System.gc();//手動去觸發虛擬機回收垃圾 42 mem=rt.freeMemory(); 43 System.out.println("忙時CUP==>"+mem/1024/1024); 44 } 45 46 } 47 48 class Customers{ 49 50 private int []custIdArray; 51 52 public Customers(String db){ 53 int num=queryDB(db); 54 custIdArray=new int[num]; 55 for (int i = 0; i < num; i++) { 56 custIdArray[i]=i; 57 } 58 } 59 int queryDB(String sql){ 60 61 return 5; 62 } 63 64 public void unrefCust(){ 65 66 custIdArray=null; 67 } 68 69 70 }
2.對象與相等性
實踐8:區別reference類別和primitive型別
這個可能比較拗口很難理解字面的意思,其實也是實踐1所說的基礎類型和對象引用之間的區別
這里主要是介紹下java1.5的一個新特性:拆箱和裝箱
Integer i1=100;//等同於new Integer(100) 這里就是一個自動裝箱的過程
int k=i1;// 自動拆箱的過程
更多關於拆箱與裝箱 可以移步http://www.cnblogs.com/danne823/archive/2011/04/22/2025332.html
關於包裝類的緩存:http://blog.csdn.net/yaoweijq/article/details/6021706
/** * 基礎類型和引用類型對象區別 * 1.5新特性 裝箱和拆箱 * @author lwx * TODO * 參考: * 2014-5-13 下午2:15:45 */ public class Lesson8 { public static void main(String[] args) { //八大基礎類型和對應的裝箱類 /* boolean char byte short int float long double 對應的對象為 Boolean Character Byte Short Integer Float Long Double*/ Integer i1=100;//等同於new Integer(100) Integer i2=100; System.out.println(i1==i2);//true i1=1000; i2=1000; System.out.println(i1==i2);//false } }
實踐9:區分==和equals
這個是java經常碰到的一個基礎知識點,即"=="和"equals"區別,何時使用==何時使用equals
總結起來可以這么說:== 對於基礎類型 比較的是vlaue,而引用類型比較的是地址,當對象不需要單純的比較地址
而需要你自己DIY的時候,請重寫equals方法吧
至於何時使用,可以這么說:==經常是基礎類型在用,引用類型的基本不用
equals最常見,而且多數情況下你是需要重寫的
有點以偏概全,希望拍磚
實踐10:不要依賴equals()的缺省實現
不啰嗦了,直接上代碼

1 /** 2 * 3 * 重寫父類equals方法(默認重寫的是Object的equals方法) 4 * @author lwx 5 * TODO 6 * 參考: 7 * 2014-5-13 下午3:46:15 8 */ 9 public class Lesson10 { 10 11 public static void main(String[] args) { 12 13 14 BasketBall b1=new BasketBall("brand",20.0); 15 BasketBall b2=new BasketBall("brand",20.0); 16 System.out.println(b1.equals(b2));//不重寫 則調用Object equals的方法 17 } 18 19 20 } 21 22 23 class BasketBall{ 24 25 private String brand; 26 27 private double price; 28 29 public BasketBall(){} 30 31 32 public BasketBall(String brand, double price) { 33 super(); 34 this.brand = brand; 35 this.price = price; 36 } 37 38 @Override 39 public boolean equals(Object obj) { 40 41 if(null!=obj&&obj.getClass()==getClass()){ 42 43 BasketBall ball= (BasketBall) obj; 44 if(brand.equals(ball.brand)&&price==ball.price){ 45 46 return true; 47 } 48 } 49 return false; 50 } 51 52 }
上面算是比較正常的一個重寫equals的方法,后續書本作者也提到了一個情況
就是對象的變量如果不是基礎類型,也是引用類型的話,就需要額外處理了(舉個例子,比如brand改成Stringbuffer類型)
作者給出了四個解決辦法:
1.不使用Stringbuffer,繼續使用String
2.比較的時候 先將Stringbuffer對象轉出String(調用toString()方法)
3.繼承Stringbuffer,重寫equals方法 讓變量變成重寫類的類型(有點拗口)
4.放棄equals改用compare()方法
例子書本上都有,感興趣的都可以去看看
關於何時重寫equals,上一節已經表述了自己的觀點,這里補充下原作者的觀點:
實踐11~15 略
3.異常處理
實踐16:認識【異常控制流】機制
記住一個模式:try { //do something }catch(Exception e){// when exception happen to do }finally{//不管有無異常 都將執行 不受return 影響}
順序為:先執行try中的邏輯,如果正常執行,則跳轉到finally塊中執行,如果異常了,則會終止try塊中的邏輯,轉移到
catch塊中執行,最后還是會在finally完成最后的操作

1 /** 2 * @author lwx 3 * TODO 4 * 參考: 5 * 2014-5-13 下午4:40:36 6 */ 7 public class Lesson16 { 8 9 10 public static void main(String[] args) { 11 12 int i =0; 13 int k=2; 14 int addResult=0; 15 int divideResult=0; 16 try { 17 divideResult=k/i; 18 addResult=k+i;//這里將不會執行 19 } catch (Exception e) { 20 System.out.println("異常了-->"+e.getMessage()); 21 }finally{ 22 23 addResult=1; 24 divideResult=0; 25 } 26 System.out.println("addResult-->"+addResult); 27 System.out.println("divideResult-->"+divideResult); 28 29 } 30 31 32 }
實踐17:絕對不可輕視異常
當發生程序異常的時候,我們有哪些處理方式
1.捕獲並處理,防止它進一步傳播
2.捕獲並在此拋出它,傳播給它的調用者
3.捕獲它,並拋出一個新的異常給調用者
4.不捕獲這個異常,任由它傳播
這里需要用到引用一個新的概念:拋出異常,通過throws來完成
正常情況下,第一個處理方式是最常見的,實踐16中也是采用了第一種處理方式
后續三種我們將在實踐18~20中一一介紹

1 public class Lesson17 { 2 public static void main(String[] args) { 3 try { 4 test(); 5 } catch (Exception e) { 6 // TODO Auto-generated catch block 7 e.printStackTrace(); 8 System.out.println("異常處理提示"); 9 } 10 } 11 12 static void test () throws Exception{ 13 14 System.out.println(2/0); 15 16 throw new Exception(); 17 18 } 19 20 }
實踐18:千萬不要遮掩異常
在處理try塊匯總的異常時,如果catch獲取finally中又拋出異常,那么之前的異常會被覆蓋
優先級:finally>catch>try
我們知道,finally是不受之前是否異常影響的,都將會執行,但是特殊情況下finally語句照樣
也會產生異常,那到底要如何處理呢?那就是將異常存放在Vector中

1 /** 2 * 3 * 如何捕獲所有的異常 -->將異常對象存放在容器對象中 4 * 5 * @author lwx TODO 參考: 2014-5-13 下午5:02:51 6 */ 7 public class Lesson18 { 8 9 public static void main(String[] args) { 10 11 Hidden hidden = new Hidden(); 12 try { 13 hidden.readFile(); 14 } catch (FileNotFoundException e) { 15 // TODO Auto-generated catch block 16 e.printStackTrace(); 17 } catch (IOException e) { 18 // TODO Auto-generated catch block 19 e.printStackTrace(); 20 } 21 //改進過的捕獲異常的方式 22 NotHidden notHidden=new NotHidden(); 23 24 try { 25 notHidden.readFile(); 26 } catch (ReadFileException e) { 27 // TODO Auto-generated catch block 28 e.printStackTrace(); 29 //捕獲到的異常存放到容器中 30 System.out.println(e.exceptionVector().size()); 31 32 } 33 } 34 35 } 36 37 class ReadFileException extends IOException { 38 private Vector excVector; 39 40 public ReadFileException(Vector v) { 41 excVector = v; 42 43 } 44 45 public Vector exceptionVector() { 46 47 return excVector; 48 } 49 50 } 51 52 class Hidden { 53 54 void readFile() throws FileNotFoundException, IOException { 55 56 BufferedReader br1 = null; 57 BufferedReader br2 = null; 58 FileReader fr = null; 59 60 try { 61 fr = new FileReader("test.txt"); 62 br1 = new BufferedReader(fr); 63 int i = br1.read(); 64 65 fr = new FileReader("test2.txt"); 66 br2 = new BufferedReader(fr); 67 i = br2.read(); 68 69 } finally { 70 71 if (null != br1) { 72 73 br1.close(); 74 } 75 if (null != br2) { 76 77 br2.close(); 78 } 79 80 } 81 82 } 83 84 } 85 86 class NotHidden { 87 88 void readFile() throws ReadFileException { 89 90 BufferedReader br1 = null; 91 BufferedReader br2 = null; 92 FileReader fr = null; 93 Vector excVec = new Vector(2); 94 95 try { 96 fr = new FileReader("test.txt"); 97 br1 = new BufferedReader(fr); 98 int i = br1.read(); 99 100 fr = new FileReader("test2.txt"); 101 br2 = new BufferedReader(fr); 102 i = br2.read(); 103 } catch (FileNotFoundException e) { 104 // TODO Auto-generated catch block 105 e.printStackTrace(); 106 excVec.add(e); 107 } catch (IOException e) { 108 // TODO Auto-generated catch block 109 e.printStackTrace(); 110 excVec.add(e); 111 } finally { 112 113 if (null != br1) { 114 try { 115 br1.close(); 116 } catch (IOException e) { 117 // TODO Auto-generated catch block 118 e.printStackTrace(); 119 excVec.add(e); 120 } 121 } 122 if (null != br2) { 123 124 try { 125 br2.close(); 126 } catch (IOException e) { 127 // TODO Auto-generated catch block 128 e.printStackTrace(); 129 excVec.add(e); 130 } 131 } 132 if(excVec.size()>0){ 133 throw new ReadFileException(excVec); 134 } 135 } 136 137 } 138 139 }
實踐19:明確throws字據的缺點
我們在開發過程中,經常會調用一些公用的函數(作者給了一個很通俗的名稱:工蜂型函數),而這些函數
有可能會產生異常(比如調用數據庫連接的方法),這時候處理異常有兩種方式
一種是函數自身處理,一個是調用端來處理.個人偏好是函數本身來處理,否則有10處地方調用這個函數
就要捕獲10次
實踐20:細致而全面的理解throws子句
看的不是很懂,但是根據作者的demo,我理解的是作者想表達的意圖是當重寫函數的時候
函數拋出的異常范圍不能大於該函數拋出范圍,當然也可以不拋出異常(子類拋出異常的范圍不能大於父類)

1 /** 2 * 3 * 重寫父類帶有異常的方法 4 * 則子類中的方法拋出的異常必須是該異常或則該異常的父類(拋出異常范圍更大) 5 * 當然 也可以不拋出異常 6 * @author lwx 7 * TODO 8 * 參考: 9 * 2014-5-13 下午7:02:57 10 */ 11 public class Lesson20 { 12 13 14 public static void main(String[] args) throws IOException { 15 16 17 ChildClass childClass =new ChildClass(); 18 childClass.test(); 19 childClass.test2(); 20 childClass.test3(); 21 } 22 } 23 24 25 class SubClass{ 26 27 28 29 public void test ()throws FileNotFoundException{ 30 31 } 32 public void test2 ()throws FileNotFoundException{ 33 34 } 35 public void test3 ()throws FileNotFoundException{ 36 37 } 38 39 40 } 41 class ChildClass{ 42 43 44 45 public void test ()throws FileNotFoundException{ 46 47 } 48 public void test2 ()throws IOException{ 49 50 } 51 public void test3 (){ 52 53 } 54 }
實踐21:使用finally避免資源泄露
簡單的描述就是在異常處理中,java規范是通過finally來做一些善后的事情(包括釋放資源等)
實踐23:將try/catch區段置於循環之外
弦外音:不能循環調用try/catch區段,而應該在一個try/catch中調用循環
實踐27:拋出異常前線將對象恢復為有效狀態
弦外音:將狀態變量等處理放在可能處理異常之后,保證狀態不受異常影響
4.性能
實踐31:如欲進行字符串結合,StringBuffer優於String
弦外音:對於需要頻繁處理字符拼接組合的地方,請使用StringBuffer,或則對於稍微復雜的操作,請使用StringBuffer
實踐33:慎防未使用的對象
弦外音:如果一個判斷兩選一,請不要都創建之,然后根據if else 來選擇其中一個對象
換句話說,使用的時候才去創建對象
實踐34:將同步化降至最低
弦外音:盡量少用同步操作,諸如synchronized等,除非你需要同步資源
實踐35:盡可能使用stack變量
弦外音:盡量使用局部變量(stack)來代替全局變量(heap)
實踐36:使用static,final和Privatae函數以促成inlining
略,后續在認真看
5.多線程
略,對多線程感興趣的朋友可以看看,都比較基礎
6.對象
實踐59.運用接口來支持多繼承
弦外音:java雖然不支持多繼承(class a extends b,c)但是卻可以通過接口
來完成多實現(class a implements a,b ,c......)
實踐60.避免接口函數發生沖突
弦外音:假如類A實現了B,C兩個接口,但是B和C都有方法test()
這樣A要實現B還是C的test()方法呢,作者給出了解決方式,個人覺得方法很贊
java中很多庫都使用了類似的方式來解決這類問題
即讓D繼承B接口(重命名接口,避免沖突),然后class A Implements D,C
實踐61、62:
關於繼承和接口 抽象類等知識點
實踐63~66
關於類引用之間的操作 包括淺克隆 深克隆等
實踐68:在構造函數內調用non-final函數是要當心
這個記得初學java時候,比較難搞懂的一道題目,了解其機制
需要明白java對一個類的初始過程
我們結合代碼來看下
1 /** 2 * 構建子類構造的時候會調用父類的構造函數 3 * 而父類的構造函數中有調用lookup() 注意 此時的loopup()調用的是子類的loopup() 4 * 而此時子類中的變量都還沒初始化 都是默認值 因此num=0調用之后val也是0 5 * 下面打印的語句順序可以參考 就明白了 6 * @author lwx 7 * @TODO 參考: 8 * @createtime 2014-5-13 下午7:44:35 9 */ 10 public class Lesson68 { 11 12 public static void main(String[] args) { 13 14 Derived derived=new Derived(); 15 System.out.println(derived.value()); 16 17 18 } 19 20 } 21 22 class Base { 23 24 private int val; 25 26 public Base() { 27 System.out.println(" Base()"); 28 val = lookup(); 29 } 30 31 public int lookup() { 32 System.out.println(" Base lookup"); 33 // TODO Auto-generated method stub 34 return 5; 35 } 36 37 public int value() { 38 System.out.println(" Base value"); 39 return val; 40 } 41 } 42 43 class Derived extends Base{ 44 private int num=10; 45 public int lookup(){ 46 System.out.println(" Derived lookup"); 47 System.out.println(" Derived lookup num=="+num); 48 return num; 49 50 } 51 public Derived(){ 52 System.out.println(" Derived()"); 53 System.out.println(" Derived Derived() num=="+num); 54 } 55 56 }
PDF和筆記源碼下載地址:http://download.csdn.net/detail/draem0507/7342879