Java編程常見缺陷匯總(一)
[案例1】
1 public boolean equalNode(JudgeNode a, JudgeNode b) { 2 return a.getId() == b.getId(); 3 }
【點評】
應在JudgeNode類裏定義equals()方法(估計剛從面向過程語言"轉行"過來...)。
【案例2】
1 public String[] getMsg() { 2 List<String> msgList = new ArrayList<String>(2); 3 msgList.add(linkStatus.get(AC)); msgList.add(getLinkMsg());4 String[] result = new String[msgList.size()]; 5 msgList.toArray(result); 6 return result; 7 }
【點評】
可簡化為return new String[]{linkStatus.get(AC), getLinkMsg()};
【案例3】
1 private boolean bInteger(Object[][] values, int i) { //開發環境真實代碼 2 boolean b = false; 3 try { 4 b = values[0][i] instanceofInteger; 5 } catch(Exception e) { 6 logger.error("values[0][i] instanceof Integer error:"+e); 7 b = false; 8 } 9 return b; 10 }
【點評】
1. instanceof為運算符,不會拋出空指針等異常;從上下文得知,values[0][i]也不會出現越界等異常。
因此,通過instanceof判斷類型時無須捕獲異常。進一步,bInteger()方法毫無意義,完全可以在調用處直接用instanceof判斷。
2. 使用slf4j日誌組件時,logger.error(與log.warn)接受Throwable參數,以打印異常名和詳細的堆棧信息(可能內部調用e.printStackTrace())。
但書寫打印語句時,需要註意格式。例如:
1 logger.error("Best print: ", e); 2 logger.error("Good print: {}", e); //a. 3 logger.error("Bad print: " + e); //b. 或 + e.toString() 4 logger.error("Bad print: " + e.getMessage()); //c. 或: {}", e.getMessage())
a句仍可打印異常名和堆棧信息,但多輸出一對花括號"{}";b句僅打印異常名;c句打印異常消息字符串。以空指針異常(Runtime異常)為例,b句打印"java.lang.NullPointerException",c句打印"null"(過於簡陋)。
【案例4】
1 public DBBatch(int count) throws SQLException { 2 con = ConnnectionManager.getConnection(DBConsts.DB_SOURCE_NAME); 3 preparedStatement = con.prepareStatement(DBConsts.SQL); 4 result = preparedStatement.executeQuery(); 5 this.count = count; 6 } 7 public void close() { //close result\statement\connection 8 }
【點評】
應避免在構造函數裏申請資源。若構造函數DBBatch(int count)裏preparedStatement.executeQuery()發生異常,上面的preparedStatement、con將無法關閉。此時,外部獲取不到DBBatch對象引用,也就無法調用DBBatch.close()方法,導致資源泄露。
【案例5】
1 private static String listToString(List<String> columnList) { 2 StringBuilder colName = new StringBuilder(1000); //1 3 for(String s : columnList) { 4 colName.append(s + ","); 5 } 6 colName.deleteCharAt(colName.length() - 1); //2 7 return colName.toString(); 8 }
【點評】
1. new StringBuilder(1000)初始化指定的長度過大,造成空間浪費。
相關資料參見《淺談JAVA中HashMap、ArrayList、StringBuilder等的擴容機制》。
2. columnList為空集合時,例如List<String> columnList = Collections.emptyList(),會拋出下標越界異常(StringIndexOutOfBoundsException)。
3. 可用String.join(",", columnList)代替。若期望"[a, b, c, d]"的輸出格式,直接用columnList.toString()即可。
【案例6】
1 private static boolean checkName1(String name) { //循環10000000次,耗時350ms 2 List<String> nameList = new ArrayList<String>(); 3 nameList.add("LiLei"); nameList.add("HaMeimei"); nameList.add("HeDan"); 4 nameList.add("Jame"); nameList.add("Lucy"); nameList.add("Beth"); 5 if(nameList.contains(name)) { 6 return true; 7 } 8 return false; 9 } 10 11 private static boolean checkName2(String name) { //循環10000000次,耗時1729ms 12 Set<String> nameSet = new HashSet<String>() { 13 { add("LiLei"); add("HaMeimei"); add("HeDan"); 14 add("Jame"); add("Lucy"); add("Beth"); 15 }}; 16 return nameSet.contains(name); 17 } 18 19 private static final List<String> NAME_LIST = new ArrayList<String>() { 20 { add("LiLei"); add("HaMeimei"); add("HeDan"); 21 add("Jame"); add("Lucy"); add("Beth"); 22 }}; 23 private static boolean checkName3(String name) { //循環10000000次,耗時110ms 24 return NAME_LIST.contains(name); 25 } 26 27 private static final Set<String> NAME_SET = new HashSet<String>() { 28 { add("LiLei"); add("HaMeimei"); add("HeDan"); 29 add("Jame"); add("Lucy"); add("Beth"); 30 }}; 31 private static boolean checkName4(String name) { //循環10000000次,耗時52ms 32 return NAME_SET.contains(name); 33 } 34 35 private static boolean checkName5(String name) { //循環10000000次,耗時46ms 36 return "LiLei".equals(name) || "HaMeimei".equals(name) || "HeDan".equals(name) || 37 "Jame".equals(name) || "Lucy".equals(name) || "Beth".equals(name); 38 }
【點評】
1. 不要使用"if(expression) return true; else return false;"的寫法,應改為"return expression;"。
2. List的contains()方法內部調用indexOf()遍歷查找整個數組,效率較低。若涉及大規模數據操作,應選用Map等容器。
3. 本案例采用《Java字符串連接的多種實現方法及效率對比》中的計時方法,統計checkName1~5()的執行效率。結果一目了然~
【案例7】
1 Map<String, String> apMap = constructApMap(); 2 for(String apId: apMap.keySet()) { 3 String apType = apMap.get(apId); 4 ... ...
【點評】
建議使用apMap.entrySet遍歷Map,而不是keySet+get(實際上遍歷兩次)。雖然Map get方法"很快",但也不能浪費!
相關資料參見《keySet 與entrySet 遍歷HashMap性能差別》。
entrySet簡化語法(Java 8函數式編程):
1 Map<String, Object> map = new HashMap<>(); 2 map.forEach((key, value)-> { 3 ... ... 4 });
【案例8】
1 private double[] devanningArray(List<Double> origin) { 2 double[] target = new double[origin.size()]; 3 for(int i = 0; i < origin.size(); ++i) { 4 target[i] = origin.get(i); 5 ... ...
【點評】
ArrayList隨機訪問,時間復雜度O(1);LinkedList隨機訪問,時間復雜度O(n)。
建議:1) 優先for-each,即for(e:list){};2) 若必須隨機訪問,建議從接口契約上約束,如fun(ArrayList array){}。
附Java容器類的特征概述:
ArrayList封裝數組,適用於“讀多寫少”的場景; LinkedList雙向鏈表,適用於“寫多”的場景,隨機訪問效率低; Vector、Stack雞肋 HashMap、HashTable關聯容器,HashTable同步 TreeMap紅黑樹實現,有序:按邏輯大小排序 LinkedHashMap有序:按插入順序排序 HashSet、TreeSet、LinkedHashSet封裝對應的Map ConcurrentHashMap ConcurrentLinkedQueue、ConcurrentLinkedDueue無鎖隊列,慎用 ArrayBlockingQueue、LinkedBlockingQueue 阻塞隊列 CopyOnWriteArrayList、CopyOnWriteArraySet 慎用 工具、封裝類: Arrays.asList、Collections.unmodifiableList\Set\Map\Collection Collections.synchronizedList\Set\Map\Collection
Java編程常見缺陷匯總(一)