Java开发人员最容易出现的几类错误
一、把数组转成ArrayList
List<String> list = Arrays.asList(arr);
//以下带伪代码来自Arrays类中
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
}
使用Arrays.asList()
方法可以得到一个ArrayList,但是得到这个ArrayList
其实是定义在Arrays类中的一个私有的静态内部类。这个类虽然和java.util.ArrayList
同名,但是并不是同一个类。java.util.Arrays.ArrayList
类中实现了set()
, get()
, contains()
等方法,但是并没有定义向其中增加元素的方法。也就是说通过Arrays.asList()
得到的ArrayList的大小是固定的。
二、在循环中删除列表中的元素
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d")); for(int i=0;i<list.size();i++){ list.remove(i); } System.out.println(list);
输出结果:
[b,d]
以上代码的目的是想遍历删除list中所有元素,但是结果却没有成功。原因是忽略了一个关键的问题:当一个元素被删除时,列表的大小缩小并且下标也会随之变化,所以当你想要在一个循环中用下标删除多个元素的时候,它并不会正常的生效。
也有些人知道以上代码的问题就由于数组下标变换引起的。所以,他们想到使用增强for循环的形式:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d")); for(String s:list){ if(s.equals("a")){ list.remove(s); } }
但是,很不幸的是,以上代码会抛出ConcurrentModificationException
,有趣的是,如果在remove操作后增加一个break,代码就不会报错:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a","b","c","d")); for(String s:list){ if(s.equals("a")){ list.remove(s); break; } }
迭代器被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 迭代器会马上抛出java.util.ConcurrentModificationException
异常。
所以,正确的在遍历过程中删除元素的方法应该是使用Iterator:
ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d")); Iterator<String> iter = list.iterator(); while (iter.hasNext()) { String s = iter.next(); if (s.equals("a")) { iter.remove(); } }
next()
方法必须在调用remove()
方法之前调用。如果在循环过程中先调用remove()
,再调用next()
,就会导致异常ConcurrentModificationException
。原因如上。
三、迷之求和
public void test_add(){ int num = 0; for (int i = 0; i < 100; i++) { num = num++; } System.out.println(num); }
最终 num
结果为 0,num++
根本没起啥作用。因为后++,是先用结果,在++操作,不会给赋值。正确写法是:num = ++ num;
四、无用日志
public boolean ruleEngine(MatterReq req) { try { // 业务流程 } catch (Exception e) { logger.error(e); // 只打异常,不打入参信息 } }
五、耗时遍历
public void test_LinkedList() { // 初始化100万数据 List<Integer> list = new LinkedList<Integer>(1000000); // 遍历求和 int sum = 0; for (int i = 0; i < list.size(); i++) { sum += list.get(i); } }
乍一看可能觉得没什么问题,但是这个遍历求和会非常慢。主要因为链表的数据结构,每一次list.get(i)
都是从链表的头开始查找,与ArrayList
不同,LinkedList
它时间复杂度是O(n)。那如果说你不知道对方传过来的是LinkedList
还是ArrayList
呢,其实可以通过list instanceof RandomAccess
进行判断。ArrayList
有随机访问的实现,LinkedList
是没有。同时也可以使用增强的for循环或者Iterator
进行遍历。