0x0、要点提炼
- 「普通for循环」类似代码,Java不报错,Kotlin却数组越界,因「循环条件不一样」
Java:先判断是否满足条件 → 执行循环体 → 自增
Kotlin:遍历的是范围,直接进循环体
- 「增强for循环」= while循环 + 迭代器Iterator
- 「迭代器的设计哲学」→ 将 遍历行为 与 被遍历对象 分离,无需关心容器底层结构;
- 「ConcurrentModificationException」异常原因(通过两个字段配合)
modCount → 记录列表结构修改次数,调用列表的remove方法,此值+1
expectedModCount → 预计修改次数,调用Itr迭代器中的remove方法时,会调用列表的remove方法,而后将modCount赋值给expectedModCount,即保证遍历过程中两个值是相等的;
如果此时调用列表的remove方法,modCount增加1,而迭代器中的expectedModCount没变,两者不等,就会引发ConcurrentModificationException异常。
- 「fail-fast(快速失败)」
做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报,一种用于提前预警的Bug检测机制;在集合中的应用就是:在遍历一个集合时,当集合结构被修改,直接抛出异常。
- 「规避ConcurrentModificationException的几种方法」
- 单线程:使用迭代器进行remove;
- 多线程:在使用迭代器处加锁(如synchronize);
- 使用fail-safe(安全失败)机制的同步容器,如CopyOnWriteArrayList,在java.util.concurrent包中;
- 「Kotlin中使用toList()后可以规避异常的原因」
创建了新的ArrayList用作遍历,remove移除的是旧ArrayList的元素,故互不影响
0x1、起因
寒冷的午后,在一个交流群里,一位开发者盆友抛出了下面的问题:
同时附带两张截图:
刚好在写代码的我,随手点开了 toList() 的源码:
惯性思维 回了句:涉及到可变列表和不可变列表吧
接着截了个 Collection.kt 的文件结构图后,追加:
其实我并没有理解他的问题,就开始跟起了RecyclerView的源码,再经历过一番排查得出:
应该和 Iterable,普通for循环,增强for循环有关
然后被拉去开了个两个半小时的用例评审…回来看到问题还没解决,看了聊天记录,捋了一下才弄懂他的问题:
1、普通for循环(下标遍历),类似的代码,Java不报错,Kotlin却抛 IndexOutOfBoundsException;
2、增强for循环(foreach),remove移除,都会抛 ConcurrentModificationException
看不懂?写个简单的代码演示下问题:
① Java
② Kotlin
初始化列表:
行吧,接着一个个讲解~