Java编程常见陷阱(二)

Last Modified: 2022/10/28

foreach 中添加和删除元素

for (String item : list) {
    if (Objects.equals(item, "hello")) {
        list.remove(item);
    }
}

以上代码可能会抛出 ConcurrentModificationException。即便是在单线程中运行,也可能会抛出该异常,所以这个异常让人相当迷惑。

为啥呢?因为 foreach 遍历本质上也是用 Iterator 来做的,Iterator 实例在创建时会将 list 的 modCount 记录在自己的实例变量 expectedCount 中。 list 的 modCount 是记录那些会导致 list 中元素数量发生改变(add/remove等)的操作的次数。

例如:每调用一次 add 或者 remove 都会导致 modCount 增加1。上面的例子在遍历过程中,删除一个元素,list 的 modCount 增加了1,iterator 发现 list 的 modCount 和自己记录的 expectedCount 不一样,于是抛出 ConcurrentModificationException。

如果确实需要在遍历过程中修改 list,正确方法是使用 Iterator:

Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
    String item = iterator.next();
    if (Objects.equals(item, "xx")) {
        iterator.remove();
    }
}

如果仅仅是为了删除元素,推荐使用 List#removeIf 方法。

ConcurrentHashMap 键值为 null 问题

ConcurrentHashMap 的 key 和 value 都不能为 null,否则抛空指针。但是 HashMap 的 key 和 value 都可以为 null。

大整数传递问题

Java 服务端在返回大整数(例如 Long 类型的整数)给前端时,应该转化为字符串返回给前端,否则可能会导致精度丢失的问题。Javascript 的 Number 类型 大致相当于 Java 中的 double 类型。

Long 类型能表示的最大值是2的63次方减一,在取值范围之内,超过2的53次方 (9007199254740992)的数值转化为 JS 的 Number 时,有些数值会有精度损失。

扩展说明,在 Long 取值范围内,任何2的指数次整数都是绝对不会存在精度损失的,所以说精度损失是一个概率问题。若浮点数尾数位与指数位空间不限,则可以精确表示任何整数,但很不幸, 双精度浮点数的尾数位只有 52 位。

return 覆盖问题

try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。

private int x = 0;
public int checkReturn() {
    try {
        // x 等于 1,此处不返回
        return ++x;
    } finally {
        // 返回的结果是 2
        return ++x;
    }
}

switch 空指针问题

使用 switch 的时候,要注意判空!如果传递的值是 null,是不会进入 default 分支的,而是会抛出空指针异常,看下面的例子:

public static void s(String x) {
    switch (x) {
        case "a":
            break;
        case "b":
            System.out.println("b");
            break;
        default:
            System.out.println("default");
    }
}

// 调用会抛出空指针异常
s(null);
有问题吗?点此反馈!

温馨提示:反馈需要登录