Java Debug之爬出深坑——非典型bug備忘

1. 不可變類

a.add(b); //坑!

BigDecimal爲不可變類, 所以執行運算的結果需要再返回給a

a = a.add(b);

舉一反三

String也是一個不可變類

String s="";
s+"a"; //坑!!
s=s+"a";

事實上jdk的java.lang包中 Boolean, Byte, Character, Double, Float, Integer, Long, Short, String都是不可變類

深入理解

要創建不可變類,要實現下面幾個步驟:

將類聲明爲final,所以它不能被繼承
將所有的成員聲明爲私有的,這樣就不允許直接訪問這些成員
對變量不要提供setter方法
將所有可變的成員聲明爲final,這樣只能對它們賦值一次
通過構造器初始化所有成員,進行深拷貝(deep copy)
在getter方法中,不要直接返回對象本身,而是克隆對象,並返回對象的拷貝

2. ConcurrentModificationException, Map遍歷並刪除某些Key-Value時必須用Iterator!而且僅限刪除

報錯

java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextEntry(HashMap.java:922)
    at java.util.HashMap$EntryIterator.next(HashMap.java:962)
    at java.util.HashMap$EntryIterator.next(HashMap.java:960)

代碼

 //如果信號爲空,刪除該條CompanyInfo
            for(Map.Entry<Integer, CompanyInfoWithSignalGroup> entry : map.entrySet()){
                if(entry.getValue().getSignals().size()==0){
                    map.remove(entry.getKey());//坑!沒用Iterator
                }
            }

如果你的 Collection / Map 對象實際只有一個元素的時候, ConcurrentModificationException 異常並不會被拋出。這也就是爲什麼在 javadoc 裏面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.
解決方法:在Map或者Collection的時候,不要用它們的API直接修改集合的內容,如果要修改可以用Iterator的remove()方法
由於for-each的寫法,使我們無法獲得iterator對象,所以這種遍歷方式不能進行刪除操作。只好改成了比較土的方法實現了,如下:

            Iterator<Map.Entry<Integer, CompanyInfoWithSignalGroup>> it = map.entrySet().iterator();
            while(it.hasNext()){
                Entry<Integer, CompanyInfoWithSignalGroup> entry=it.next();
                if(entry.getValue().getSignals().size()==0){
                    it.remove();
                }
            }        

3. SQL語句中的單引號

java中字符串的單引號要用單引號轉義,而不是通常的反斜槓

String str  = "''s'', \'s\'"; //'s', s

SQL中的應用:

String sqlTemplate =
    " INSERT INTO ebd_exclude_company_detail " +
    "     (company_id, company_full_name, company_short_name, company_type, company_source) " +
    " VALUES ({0}, ''{1}'', ''{2}'', ''{3}'', ''{4}''); ";
//ls是List<String>
String sql = MessageFormat.format(sqlTemplate, ls.toArray(new String[ls.size()])); // 該方法參數只接受Array,不支持List

4. 隕石坑——List.addAll(anotherList)

淺拷貝, 類似的還有:
new List<>(anotherList)

深拷貝必須重寫clone方法, 參考Java中如何克隆集合——ArrayList和HashSet深拷貝
1)Employee實現Cloneable接口
2)爲Employee類增加下面的clone()方法

@Override
    protected Employee clone() { 
        Employee clone = null; 
        try{ 
            clone = (Employee) super.clone(); 

        }catch(CloneNotSupportedException e){ 
            throw new RuntimeException(e); // won't happen 
        }

        return clone; 
    }

3)不使用拷貝構造函數,使用下面的代碼來深拷貝集合

Collection<Employee> copy = new HashSet<Employee>(org.size()); 

Iterator<Employee> iterator = org.iterator(); 
while(iterator.hasNext()){ 
    copy.add(iterator.next().clone()); 
}

4)運行相同的代碼更改原始集合,克隆集合不會也被更改。

- Original Collection after modification  [Joe: staff, Tim: staff, Frank: staff]
- Copy of Collection without modification [Frank: Developer, Joe: Manager, Tim: Develope]

5) 重要!如果Employee的成員變量包括list等集合類,需要在重寫的clone方法中單獨做深拷貝

思考:Java中集合的深拷貝比較複雜,如果遇到嵌套的List,那麼重寫Cloneable接口還不能實現完整的深拷貝。不如新建一個集合,手動新建元素實現深拷貝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章