《practical Java》讀書筆記


題記:

花了一周把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 }
View Code

實踐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 }
View Code

 

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 }
View Code

上面算是比較正常的一個重寫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 }
View Code

實踐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 }
View Code

實踐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 }
View Code

實踐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 }
View Code

實踐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

 

 

 

 

 

 

   

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM