10 个让人头疼的 bug
那个谁,今天又写 bug 了,没错,他说的好像就是我。。。。。。
作为 Java 开发,我们在写代码的过程中难免会产生各种奇思妙想的 bug ,有些 bug 就挺让人无奈的,比如说各种空指针异常,在 ArrayList 的迭代中进行删除操作引发异常,数组下标越界异常等。
如果你不小心看到同事的代码出现了我所描述的这些 bug 后,那你就把我这篇文章甩给他!!!你甩给他一篇文章,并让他关注了一波 cxuan,你会收获他在后面像是如获至宝并满眼崇拜大神的目光。
废话不多说,下面进入正题。
错误一:Array 转换成 ArrayList
Array 转换成 ArrayList 还能出错?这是哪个笨。。。。。。
等等,你先别着急说,先来看看是怎么回事。
如果要将数组转换为 ArrayList,我们一般的做法会是这样
List list = Arrays.asList(arr);
Arrays.asList() 将返回一个 ArrayList,它是 Arrays 中的私有静态类,它不是 java.util.ArrayList 类。如下图所示

Arrays 内部的 ArrayList 只有 set、get、contains 等方法,但是没有能够像是 add 这种能够使其内部结构进行改变的方法,所以 Arrays 内部的 ArrayList 的大小是固定的。

如果要创建一个能够添加元素的 ArrayList ,你可以使用下面这种创建方式:
ArrayList arrayList = new ArrayList (Arrays.asList(arr));
因为 ArrayList 的构造方法是可以接收一个 Collection 集合的,所以这种创建方式是可行的。

错误二:检查数组是否包含某个值
检查数组中是否包含某个值,部分程序员经常会这么做:
Set set = new HashSet (Arrays.asList(arr));
return set.contains(targetValue);
这段代码虽然没错,但是有额外的性能损耗,正常情况下,不用将其再转换为 set,直接这么做就好了:
return Arrays.asList(arr).contains(targetValue);
或者使用下面这种方式(穷举法,循环判断)
for(String s: arr){
if(s.equals(targetValue))
return true;
}
return false;
上面第一段代码比第二段更具有可读性。
错误三:在 List 中循环删除元素
这个错误我相信很多小伙伴都知道了,在循环中删除元素是个禁忌,有段时间内我在审查代码的时候就喜欢看团队的其他小伙伴有没有犯这个错误。
说到底,为什么不能这么做(集合内删除元素)呢?且看下面代码
ArrayList list = new ArrayList (Arrays.asList("a", "b", "c", "d"));
for (int i = 0; i < list.size(); i++) {
list.remove(i);
}
System.out.println(list);
这个输出结果你能想到么?是不是蠢蠢欲动想试一波了?
答案其实是 [b,d]
为什么只有两个值?我这不是循环输出的么?
其实,在列表内部,当你使用外部 remove 的时候,一旦 remove 一个元素后,其列表的内部结构会发生改变,一开始集合总容量是 4,remove 一个元素之后就会变为 3,然后再和 i 进行比较判断。。。。。。所以只能输出两个元素。
你可能知道使用迭代器是正确的 remove 元素的方式,你还可能知道 for-each 和 iterator 这种工作方式类似,所以你写下了如下代码
ArrayList list = new ArrayList (Arrays.asList("a", "b", "c", "d"));
for (String s : list) {
if (s.equals("a"))
list.remove(s);
}
然后你充满自信的 run xxx.main() 方法,结果。。。。。。ConcurrentModificationException
为啥呢?
那是因为使用 ArrayList 中外部 remove 元素,会造成其内部结构和游标的改变。
在阿里开发规范上,也有不要在 for-each 循环内对元素进行 remove/add 操作的说明。

所以大家要使用 List 进行元素的添加或者删除操作,一定要使用迭代器进行删除。也就是
ArrayList list = new ArrayList (Arrays.asList("a", "b", "c", "d"));
Iteratoriter = list.iterator();
while (iter.hasNext()) {
String s = iter.next();
if (s.equals("a")) {
iter.remove();
}
}
.next() 必须在 .remove() 之前调用。在 foreach 循环中,编译器会在删除元素的操作后调用 .next(),导致ConcurrentModificationException。
错误四:Hashtable 和 HashMap
这是一条算法方面的规约:按照算法的约定,Hashtable 是数据结构的名称,但是在 Java 中,数据结构的名称是 HashMap,Hashtable 和 HashMap 的主要区别之一就是 Hashtable 是同步的,所以很多时候你不需要 Hashtable ,而是使用 HashMap。
错误五:使用原始类型的集合
这是一条泛型方面的约束:
在 Java 中,原始类型和无界通配符类型很容易混合在一起。以 Set 为例,Set 是原始类型,而 Set 是无界通配符类型。
比如下面使用原始类型 List 作为参数的代码:
public static void add(List list, Object o){
list.add(o);
}
public static void main(String[] args){
Listlist = new ArrayList ();
add(list, 10);
String s = list.get(0);
}
这段代码会抛出 java.lang.ClassCastException 异常,为啥呢?
使用原始类型集合是比较危险的,因为原始类型会跳过泛型检查而且不安全,Set、Set 和 Set