前言 我們接觸Java可能已經有好幾年時間了,對於Java提供的不同關鍵字或各種機制都有一定程度上的了解,尤其常常受惠於它優秀、擴充性強大及多樣功能的標準函式庫(library),透過這些套件就可以快速開發出一個應用程式,但其中還是有許多設計和編寫上的實用方法及觀念,讓我們可以花較少的時間在除錯或優化程式碼,並產出較高效能的系統,所以當然需要來好好了解這些知識點。所以此篇為 Peter Haggar 所著寫的 Practical Java Programming Language Guide 之讀後心得筆記,目標就是讓我們寫出更正確、高效率且穩定的Java程式,且非常適合我們中高階的Programmer來閱讀。
Java傳遞參數都只有Call by value
Java中所有引數(Argument Value)都是 call by value 的方式來傳遞資訊
Java 對於 primitive type 和 reference type 的處理是不太一樣的
函數參數收到primitive type的值後,會在stack產生一個同樣值得副本
函數參數收到object reference(儲存物件在Heap的記憶體位址)後,一樣也會產生object reference的副本(一個Address)
primitive type 的複件(copy)隨便賦值都不影響,但object reference 的複件(copy)就必須要小心使用
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Employee { String name; String empId; public Employee (String name, String empId) { this .name = name; this .empId = empId; } public String toString () { return String.format("Employee{name=%s, empId=%s}" , name, empId); } } public class PassByValueExample { public static void doEmployee (Employee e, int v) { v = 100 ; e.name = "KevinFang" ; e = new Employee ("Red" , "54321" ); System.out.println(String.format("During Modify %s and v = %d" , e, v)); } public static void main (String[] args) { int v = 10 ; Employee kevin = new Employee ("kevin" , "12345" ); System.out.println(String.format("Before Modify %s and v = %d" , kevin, v)); doEmployee(kevin, v); System.out.println(String.format("After Modify %s and v = %d" , kevin, v)); } }
Output
1 2 3 Before Modify Employee{name =kevin, empId =12345} and v = 10 During Modify Employee{name =Red, empId =54321} and v = 100 After Modify Employee{name =KevinFang, empId =12345} and v = 10
變數v傳進去方法出來後還是維持不變,但在第17行對傳進來的物件參考進行修改,出來後就改變了,這是因為此時此刻doEmployee的參數e和main的物件參考kevin都是指向同一個Heap的記憶體位址。在第18行把參數e的位址賦予新的物件,因為原本的位址就是一個copy,所以也不會影響到其他地方,最後e就會改指向新的位址,如下圖所示。
不變的常數或物件參考使用關鍵字final
final可以放在類別(Class)、函數(method)以及變數(variable)之前來表示不同作用
類別之前表示該類別禁止被繼承 (Ex: String)
函數之前表示繼承他的子類別無法覆寫此方法 (Ex: Java.lang.Object的getClass())
變數之前則代表此變數是一個常數,且不能再被賦予其他值 (Ex: PI)
物件參考之前表示必須永遠都要指向Heap裡面的同一個物件的位址
final的變數通常用來表示常數資料
final的常數名稱習慣上會全部大寫
宣告時或建構式(Constructor)中一定要有賦值的動作
在Java SE API中會宣告為final的類別或方法,通常與JVM物件或作業系統資源管理有密切相關,因此不希望API使用者繼承或重新定義 (良葛葛)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 final double PI = 3.14 ;PI = 1.23 ; ------------------------ class Employee { private int salary; public Employee (int salary) { this .salary = salary; } public int getSalary () { return salary; } public void setSalary (int salary) { this .salary = salary; } } public class Finance { static final Employee kevin = new Employee (22000 ); public static void main (String[] args) { kevin.setSalary(100000 ); }
所有non-static函式都可以被覆寫
只要函式具有是private、static或final關鍵字就能夠阻止被子類別覆寫(Override)
兩個方式防止函式被子類別覆寫
宣告函式所在class為final,但也暗示此class的所有函式皆為final,因為也沒有其他類別可以繼承
宣告此函式為final
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Father { public int num = 10 ; public final int getNum () { return num; } } class Son extends Father { public int getNum () { return num + 1 ; } } final class Father { public int num = 10 ; public int getNum () { return num; } } class Son extends Father { }
關鍵字final的應用在library就隨處可見,像是String類別在宣告前就加上final關鍵字,防止我們來去繼承它,另外Object類別中的許多函式都有加上final避免被覆寫,如下官方API所示
多型(Polymorphism) 優於 instanceof 使用 instanceof 能夠得到物件之間是否具有某種繼承或實作關係
Bad example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 interface Employee { int getSalary () ; } class Manager implements Employee { private static final int mgrSal = 40000 ; public int getSalary () { return mgrSal; } } class Programmer implements Employee { private static final int prgSal = 50000 ; private static final int bonus = 10000 ; public int getSalary () { return prgSal; } public int getBonus () { return bonus; } } public class Payroll { public int calcPayroll (Employee emp) { int totalSalary = emp.getSalary(); if (emp instanceof Programmer) { totalSalary += ((Programmer) emp).getBonus(); } return totalSalary; } public static void main (String[] args) { Payroll pr = new Payroll (); Employee programmer = new Programmer (); Employee manager = new Manager (); System.out.println("Payroll for Programmer is " + pr.calcPayroll(programmer)); System.out.println("Payroll for Manager is " + pr.calcPayroll(manager)); } }
多型取代 instanceof
日後隨需求改變可能又會多出HR等繼承Employee介面的類別
C++大師Scott Meyers 曾提到:「如果物件的型別是T1就做某事,接著如果物件的型別是T2就做另外一件事情,請給自己一個巴掌」
透過多型改善程式的物件導向純度,同時提高維護性及擴充性
Good example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 interface Employee { int getSalary () ; int getBonus () ; } class Manager implements Employee { private static final int mgrSal = 40000 ; private static final int bonus = 0 ; public int getSalary () { return mgrSal; } public int getBonus () { return bonus; } } class Programmer implements Employee { private static final int prgSal = 50000 ; private static final int bonus = 10000 ; public int getSalary () { return prgSal; } public int getBonus () { return bonus; } } public class Payroll { public int calcPayroll (Employee emp) { int totalSalary = emp.getSalary() + emp.getBonus(); return totalSalary; } public static void main (String[] args) { Payroll pr = new Payroll (); Employee programmer = new Programmer (); Employee manager = new Manager (); System.out.println("Payroll for Programmer is " + pr.calcPayroll(programmer)); System.out.println("Payroll for Manager is " + pr.calcPayroll(manager)); } }
必要時才使用 instanceof 常見的使用時機
使用的library設計不當
處理向下轉型(Down Casting)的時候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Animal { int leg; } class Koala extends Animal { int leg = 2 ; } class Kangaroo extends Animal { int leg = 4 ; } class Rhinoceros { } public class Payroll { public static void main (String[] args) { Animal animal1 = new Koala (); Object animal2 = new Kangaroo (); System.out.println(animal1.leg); Kangaroo kangaroo = (Kangaroo)animal1; Rhinoceros rhinoceros = (Rhinoceros)animal1; Kangaroo an2 = (Kangaroo)animal2; System.out.println(an2.leg); } }
第23行會在執行期拋出ClassCastException例外,可利用instanceof來確保向下轉型的安全性,避免使用try-catch來處理這種異常
1 2 3 if (animal1 instanceof Kangaroo) { Kangaroo kangaroo = (Kangaroo)animal1; }
一旦不需要object reference就將它設為null 當不需要再使用某個物件,可將其物件參考(reference)設為null,Java的GC就會來回收這不需要的記憶體
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Class Customers { private int [] custIdArray; public Customers (String db) { int num = query(db); custIdArray = new int [num]; for (int i=;i<num;i++) { custIdArray[i] = } } } public class Console { public static void main (String[] args) { Customers cust = new Customers ("Oracle_db" ); cust = null ; }
其他 https://stackoverflow.com/questions/30903801/hql-order-by-expression Study Group
Java function 命名習慣
變數名稱一定要以英文字母大小寫(a-z, A-Z),金錢符號($),底線符號(_)來開頭,不能以數字或其他特殊符號為開頭。 Java關鍵字or保留字不作為命名
介面(Interface)多以形容詞或名詞來命名
類別(Class)則是應為名詞
方法(Method)應為動詞 saveMoney getCustomerName isWakeUp turnOff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 var fxkGroup = { xtype : 'checkboxgroup' , id : 'group' , width : 550 , columns : 4 , defaults: { listeners : { change: function (checkbox, newValue ) { if (newValue) { var arr = Ext.getCmp('group' ).query('checkbox' ); Ext.Array .forEach(arr, function (item, index ) { if (item.boxLabel !== checkbox.boxLabel) { item.setValue(false ); } else { console .log (item.boxLabel); } }); } } } }, items : [{ boxLabel : 'A' , inputValue : "1" }, { boxLabel : 'B' , inputValue : "2" }, { boxLabel : 'C' , inputValue : "3" }, { boxLabel : 'All' , inputValue : "4" } ] };
前言 異常處理在javs當中是一個強大而實用的機制,但同時也增加了不少複雜性 java是最早引入異常處理模型的語言 但要怎樣恰當使用異常處理也變得相當不容易 有時候處理異常 以為只是加入try catch 反而造成另外的異常發生 異常處理讓我們建立具有容錯(fault-tolerant) 穩固(robust)的錯誤處理機制和回復(recovery)
https://litotom.com/java-exception-handling-try-catch/
16 認識異常控制流(exception control flow)機制
複雜原因=>程式碼地點一下跳轉到另外一個區塊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Example { public void ex1 () throws IOException { try { ex2(); ex3(); } catch (IOException e) { System.out .println("Caught IOException in ex1()" ); throw e; } finally { System.out .println("In finally for ex1()" ); } System.out .println("Exit ex1()" ); } public void ex2 () { try { int tmp = 5 /0 ; } catch (ArithmeticException e) { System.out .println("Caught ArithmeticException in ex2()" ); } finally { System.out .println("In finally for ex2()" ); } System.out .println("Exit ex2()" ); } public void ex3 () throws IOException { try { throw new IOException(); } finally { System.out .println("In finally for ex3()" ); } } public static void main (String[] args ) { Example em = new Example(); try { System.out .println("call ex1" ); em.ex1(); } catch (Exception e) { System.out .println("Caught IOException in main" ); } }
output
1 2 3 4 5 6 7 8 call ex1 Caught ArithmeticException in ex2 ()In finally for ex2 ()Exit ex2 ()In finally for ex3 ()Caught IOException in ex1 ()In finally for ex1 ()Exit ex1 ()
1沒有執行是因為在他之前有拋出異常沒有捕獲(拋出後捕獲但又再拋出,等同於沒有捕獲),再執行完finally後就跳離function 程式在try區段內拋出異常會發生,控制流會先到catch 再到finally,接著finally後面的也會執行 如果沒有catch,則舊址到finally 輕忽 對異常發生時導致事情 會造成程式行為錯誤及況展及維護的難度
17 絕對不可以輕忽異常 異常發生不捕捉會造成目前執行續(Thread)中斷 異常產生可以做的事情有
捕捉他 防止近一步傳播(propagate)
捕捉他並再次拋出他,給呼叫端來處理
捕捉他,然後拋出新的異常給呼叫端
不捕捉她,聽任他傳播給呼叫端
3要確保拋出新的異常包含原有異常的相關資訊 草率方式是捕捉他不作為=>會好像什麼都沒發生過 把異常吞掉 => 吞噬異常比忽略回傳碼(!)(return code)異常引起更多麻煩
1 2 3 4 5 6 try { // code that could throw a FileNotFoundException } catch (FileNotFoundException e) { // do nothing } // rest of code in method
至少要輸出到log file
18 千萬不要遮掩異常 處理前面拋出的異常後,再catch或finally又拋出異常,會讓前面的異常被遮掩掉,只剩下最後生成的異常傳給呼叫端 =>想知道異常的罪魁禍首,就不能遮掩異常(串肉粽)
1 2 3 4 5 6 7 8 9 10 11 public static void foo () throws Exception { try { throw new Exception ("first Exception" ); } catch (Exception e) { throw new Exception ("second Exception" ); } finally { throw new Exception ("third Exception" ); } } foo ();
/1 和 / 2被3遮掩
19 明察throws子句的缺點 throws 向呼叫的函式發出預警 woker method 增加了一個可能拋出異常的code 就地處理 call他的人處理 慎用,只要method加上throws,會影響每一個呼叫者 沒有一定答案,系統設計時就有設計好錯誤處理的策略
20 細緻而全面理解 throws 子句 提醒呼叫者可能會發生的異常(像料靶子) 覆寫時 方法拋出的例外只能是相同或其子類別 甚至是不拋
21 使用 finally 避免資源洩漏 (V) 關掉DB連線相關設定 socket.close() connect.close()
22 不要從try區段返回return finally再就一定會執行 try有三種結果 拋出異常 try區段正常結束 再try區段執行return break continue等關鍵字,會直接離開try區段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static int method1 () { try { return 1 ; } catch (Exception e) { return 2 ; } } public static int method2 () { try { return 3 ; } finally { return 4 ; } } System.out .println(method1()); System.out .println(method2());
傳統認為return就會立刻離開,finally是特例。 return 3後控制權會移轉到finally 並蓋過此return finally這種獨特性質有可能令人困惑,陷入漫長的除厝困境無法自拔 盡可能避免在try區段中使用return break continue敘述句 如果堅持要用要確保finally存在不會改變回傳值
23 將 try/catch 區段置於迴圈之外 (V) 24 不要將異常用於流程控制
1 2 3 4 5 6 7 8 9 try { while (true ) { throw new CustomException (); } } catch (CustomException e) { }
用異常來控制流程是使用異常來中斷迴圈,雖可執行 但效率低 且語意含糊 難以維護 記住=> 異常處理(Exception handling)只用於異常情況,不要把它當作是流程控制的方式
25 不要每逢出錯就使用異常 異常處理的初衷是作為錯誤處理技術(Error Handling techniques)的一份更可靠的替代品 避免使用在流程控制及揭發非錯誤的情況 Good example
1 2 3 4 5 6 7 int data ;MyInpustStram in = MyInputStream ('test.txt');data = in.getData();while (data != 0) { // Do something with data data = in .getData (); }
檢查回傳值是不是0 0就表示再資料的尾端 此方法為傳的的回傳碼(return code)進行錯誤檢查 Bad example
1 2 3 4 5 6 7 8 9 10 11 int data;MyInpustStram in = MyInputStream('test .txt ') ; try { while (true ) { data = in .getData() ; } } catch (NoMoreDataException e) { }
當while迴圈裡面的data不再是0的時候就拋出NoMoreDataException, 通常建議是 出乎程式可預料的行為才使用異常,前面已經預期最後一定會到達文件的底端0,所以拋出異常在這邊就很怪 異常必須用在符合意義的地方 也就是出現非尋常情況 Bad example違反原則 直接回傳一個0來條件判斷更快速 更直觀
26 在建構式中拋出異常