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接口还不能实现完整的深拷贝。不如新建一个集合,手动新建元素实现深拷贝。

你可能感兴趣的:(java)