阿里巴巴编码规范认证:深入解析foreach循环中的remove/add操作及最佳实践

背景

阿里巴巴编码规范认证:深入解析foreach循环中的remove/add操作及最佳实践

阿里云上有阿里巴巴编码标准认证。我估计时间成本很低,而且多认证也没什么坏处,所以就花了1毛钱报名了。注册了这个认证之后,可以在链接下下载编码规范,然后参加考试应该就可以了。

48页的说明书其实每次读起来都需要花费一些时间,因为每次读的时候我都会发现上面有一些我不相信的东西。我需要证明这一点。过去已经被证明的东西,可能会因为JDK版本的升级和迭代而需要继续被证明。下面是一些证明过程。

案例一

标准文本

[强制] 不要在 foreach 循环中执行元素删除/添加操作。请使用Iterator方法删除元素。如果并发操作需要锁定Iterator对象。

正面例子:

列表列表 = new ArrayList();

列表.add(“1”);

列表.add(“2”);

Iteratoriteraot = list.interator();

while(iterator.hasNext()){

字符串项 =iterator.next();

if (删除元素的条件) {

迭代器.remove();

反例:

for(字符串项:列表){

if("1".equals(item)){

列表.删除(项目);

注:上面代码的执行结果肯定会出乎大家的意料。那么尝试用“2”替换“1”,会是相同的结果吗?

证明

1.首先根据计数器示例文本运行测试(test1)

列表中有2个元素,去掉1个,就剩下1个。这应该是大多数人所期待的。

2. 按照说明操作,将“1”替换为“2”来运行测试(test2)

这里,“2”并没有按照预期被移除,而是抛出了并发修改异常。点击异常的地方

3. 按照异常提示进行操作。找到抛出异常的地方,看看是哪个方法抛出了异常:

4.对源码进行分析:

抛出并发修改异常的条件是modCount!=expectedModCount。

5.根据这个条件,我做出一个猜想:一次操作将两者的值改为不同,因为这里调用了remove修改方法。自然,我猜测是remove方法所做的修改。我们看一下remove方法的源码:

6. 果然源码中添加了modCount++,但是expectedModCount没有修改。猜想被证明了。运行remove后,需要判断for(String item: list)。这时候就会调用迭代器的next方法。这样,我就明白为什么上面的test2会抛出异常了。那么我们思考一下为什么test1不抛出异常呢?

7.我们来调试test1的情况1

运行remove方法后,可以看到此时modCount!=expectedModCount,但是此时只执行了hasNext(),并判断了cursor != size。此时不会执行next方法,因此不会产生异常。下面再次使用列表时,迭代器为新的迭代器,且modCount=expectedModCount;

综上所述

如果列表在for循环中调用remove方法,将会抛出并发修改异常。但是,如果只修改第一项然后返回,则属于异常,因为此时不会调用next方法来判断modCount和expectedModCount是否相等。

使用代码规范推荐的迭代器,底层的remove方法会一起修改modCount和expectedModCount,所以单线程不会出现并发问题。作为类的成员变量,在多线程情况下是否会被修改是不确定的。

思考问题

执行以下代码的结果是什么?

案例2

标准文本

【强制】在JDK7及以上版本中,Comparotor实现类必须满足以下三个条件,否则Arrays.sort和Collections.sort将抛出IllegalArgumentException。

注:三个条件如下

1)x和y的比较结果与y和x的比较结果相反。

2)x>y,y>z,则x>z。

3)x=y,则x和z的比较结果与y和z的比较结果相同。

反例:下面的例子没有处理相等的情况。交换两个对象的判断结果不互斥,不满足第一个条件。实际使用中可能会出现异常情况。

新比较器(){

@覆盖

publicint比较(学生o1,学生o2){

returno1.getId()>o2.getId()?1:-1;

证明

1、我们先看一下反例在实际使用中会抛出哪些异常。

2、测试发现Collections.sort和Arrays.sort都会抛出错误,说必须实现Comparable接口,而不是Comparator接口。 Comparable接口不需要满足规范中提到的自反性、传递性和对称性。

那么规范为什么这么说呢?

3.我查看了Collections类的源码,确实有几个方法用到了Comparator类。包括反转、二分查找、最大值、最小值等多种排序。原始排序后面可以跟有 Comparator 参数。

4、但是,我在源码中传入两个参数后,跑了几个例子,并没有抛出非法参数异常。于是我又在源码中寻找线索。

Collections.sort底层使用Arrays.sort,Arrays.sort底层使用TimSort。 TimSort 会在两个地方抛出这个异常。看源码的时候,就是二分查找合并结果的时候。

5、果然,使用该排序时出现了非法参数异常。

6、具体为什么会出现异常,我设置断点,使用debug跟踪TimSort源码。一般来说,有些排序使用if(x

总结

实现Comparator的compare方法必须满足自反性、对称性和传递性。

案例3

标准文本

分析

1、从上面的总结来看,线程安全的Map的key和value不能为null。如果线程不安全,则可以为 null。大家都知道映射键需要进行哈希处理。散列 null 会导致空指针吗?

带着这个疑问,我打开了HashMap的源码,看到hash方法判断为null。如果为null,则哈希值为0。所以不会出现NPE

2、TreeMap的put方法不对key做任何判断,然后调用compare方法,会抛出NPE。

3.那么如果不对key进行特殊处理,肯定会抛出NPE。应该没有疑问。为什么有些值为空时会出现NPE?

从上面的源码我们可以知道ConcurrentHashMap就是这么处理的,这算是强制的。

4.并且Hashtable也是强制的。

5、为什么要设计线程安全的容器,让key和value不能为null?我在网上找到了班级设计师Lea的原话。主要意思是map需要实现containsKey和containsValue方法。这个方法实际上是使用get(XX)来实现null的情况。如果为null,则很难区分是因为不存在还是值为null。

6.什么是线程不安全,就是在单线程中处理。以下是TreeMap中containsValue的处理。如果输入为 null 并且有一个对象值为 null,则为 true。

总结

常用的四种Map中线程安全的Map的key和value不能为null。线程不安全的值可以为 null。 TreeMap 的 key 不能为 null。

案例4

标准文本

【强制】多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中一个任务没有捕获抛出的异常,其他任务就会自动终止。如果在处理定时任务时使用ScheduledExecutorService,就不会出现这样的问题。

证明

1.首先让Timer运行多个TimeTask,使得其中一个不捕获抛出的异常。

这段代码的意思是在10秒内运行两个定时任务,其中定时任务每10ms前后打印一次。另一个抛出异常,抛出异常后都停止了。

2、从Timer源码可以看出,本质上是通过队列来维护多个任务的。处理的时候,整个过程都是try catch作为一个整体。然后发生异常,整个过程停止。

3、再次验证ScheduledExecutorService的使用。可以看到抛出异常的线程运行一次后就停止了,而另一个线程继续运行。

4、从源码中可以看出,如果某个工作线程出现问题,会直接从工作队列中移除,不影响其他。

总结

与Timer相比,ScheduledExecutorService可以避免多个任务之间出现问题时产生的副作用。

案例5

标准文本

【强制】使用工具类Arrays.asList()将数组转换为集合时,不能使用其与修改集合相关的方法。它的 add/remove/clear 方法将抛出 UnsupportedOperationException 异常。注意:asList的返回对象是一个Arrays内部类,没有实现集合修改方法。 Arrays.asList体现了适配器模式,仅转换接口,后台数据仍然是数组。

String[] str = new String[] { "yang", "hao" };

列表列表 = Arrays.asList(str);第一种情况:list.add("yangguanbao");运行时异常。第二种情况:str[0] = "changed";也将进行相应修改,反之亦然。

证明

1.Arrays.asList() 将数组转换为集合,然后添加元素。测试已运行并按预期抛出异常。

跟踪源码可知,asList虽然生成了一个ArrayList,但它并不是java.util.ArrayList,而是在Arrays中自定义的。此类不支持这些更新操作。

总结

请注意集合类中返回子集或转换类型的操作。它们可能返回内部定义的类,而不是我们通常使用的类。这些类对某些操作施加限制。

思考问题

以下测试等级体现了哪些规范?

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

25阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

25阅读