1. 程式人生 > >Java編程常見缺陷匯總(一)

Java編程常見缺陷匯總(一)

dstat nbsp https tor delet void ont oid bound

[案例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] instanceof
Integer; 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編程常見缺陷匯總(一)