要了解这个为什么报错,我们需要知道两点:
1、增强for循环到额原理是什么?
为了给大家解释清楚这个问题,我特意找到了.class文件,让大家看看增强for的真身:
看到编译后的代码,我们发现底层还是有迭代器实现的,并且,并且,并且,你会发现它调用的是list的remove方法,但是这却不是报错的根源,咱们得继续看下面的源码分析
2、fast-failed机制什么时候会触发?
从报错信息中看,remote方法没有报错,报错的是next方法,因此我贴出源码
这里面有快速失败的检查,所以最终结论是,我们也不能用增强for删除。那怎么办呢?
最后介绍一种正确的方法(推荐):
//3、迭代器iterator遍历 Integer baseNum = 5; //以这个为基础 删除掉>=这个值的元素 System.out.println("剩余长度:" + list.size() + "---" + list); Iterator<Integer> it = list.iterator(); while (it.hasNext()) { Integer x = it.next(); if (x >= baseNum) { it.remove(); //list.remove(x); //java.util.ConcurrentModificationException } } System.out.println("剩余长度:" + list.size() + "---" + list); 输出结果为: 剩余长度:9---[2, 1, 3, 5, 8, 6, 2, 5, 9] 剩余长度:4---[2, 1, 3, 2]
bingo,终于拿到了我们想要的结果了。但此处一定要注意,一定要注意,一定要注意。重说三,我们remove的时候,一定只能使用迭代器的remove方法,否则也还是会报错的,重点一定要注意。
最后,我再介绍两种也能同样达到效果的删除方法,但是并不推荐这么去做,知道就行了
方法一:使用CopyOnWriteArrayList 支持并发修改的List来代替,缺点就是效率低
方法二:采用对List倒序遍历的方式。缺点是:理解难道相较于迭代器偏大,需要结合List内部实现才能真正理解。当然因为size需要实时计算,速度上也不占优势
Integer baseNum = 5; System.out.println("剩余长度:" + list.size() + "---" + list); for (int i = list.size() - 1; i >= 0; i--) { //for (int i = 0; i < list.size(); i++) { if (list.get(i) >= baseNum) list.remove(i); } System.out.println("剩余长度:" + list.size() + "---" + list);
这里再说说关于Map的删除情况,怎么删除才能不抱错呢?
这样子,采用map的remove方法,也报错:
public static void main(String[] args) { Map<Integer, Integer> map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(5, 5); map.put(3, 3); map.put(10, 10); Iterator<Integer> iterator = map.keySet().iterator(); while (iterator.hasNext()) { Integer key = iterator.next(); if (key >= 5) { map.remove(key); } } System.out.println(map); } 报错:Exception in thread "main" java.util.ConcurrentModificationException
由此课件,map也有类似情况,因此此处举一例,map可以安全删除的例子
代码同上,只需要调用iterator.remove();即可,不要用map.remove(key);
3、使用场景
一句话:集合的使用场景有哪些,这个就有哪些。所以显然:everywhere!
小彩蛋
Map中的putIfAbsent、computeIfAbsent、computeIfPresent、compute
put方法参考:
public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); System.out.println(map.put("1", 2)); //null 如果是添加成功,返回null System.out.println(map.put("1", 3)); //2 如果已经存在此key了 会用新值覆盖旧值 然后把oldValue返回出来 System.out.println(map); //{1=3} }
putIfAbsent
如果给定的key不存在(或者key对应的value为null),就把value放进去,并返回null;
如果存在,返回当前值(不会把value放进去);
public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); System.out.println(map.putIfAbsent("1", 2)); //null 如果是添加成功,返回null System.out.println(map.putIfAbsent("1", 3)); //2 如果已经存在此key了,不会覆盖此value值。 返回的还是oldValue System.out.println(map); //{1=2} }
computeIfAbsent(重要)
如果给定的key不存在(或者key对应的value为null),就去计算mappingFunction的值(计算结果决定了返回值有两种情况如下,我们姑且叫计算的结果为newValue):
若key已经存在,就不会执行mappingFunction。返回oldValue
1.newValue == null,不会替换旧值。返回null
2.newValue != null,替换旧值。返回新值
public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); System.out.println(map.computeIfAbsent("1", key -> Integer.valueOf(key) + 2)); //3 计算出来的newValue放进去,并且返回newValue System.out.println(map.computeIfAbsent("1", key -> Integer.valueOf(key) + 3)); //3 key已经存在了,不作为 System.out.println(map.computeIfAbsent("2", key -> null)); //3 key虽然不存在,但计算出为null,所以不作为,并且返回null System.out.println(map); //{1=3} }
computeIfPresent
和上面相反,当key存在且对应的oldValue不为null时,执行计算,否则啥都不做。
public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); System.out.println(map.computeIfPresent("1", (k, v) -> Integer.valueOf(k) + v)); //null key不存在,不作为 且返回null System.out.println(map.computeIfPresent("1", (k, v) -> Integer.valueOf(k) + v)); //null key还是不存在,不作为 且返回null map.put("1", 1); System.out.println(map.computeIfPresent("1", (k, v) -> Integer.valueOf(k) + v)); //2 key存在,开始计算。把计算的结果覆盖掉,然后返回计算的结果 System.out.println(map); //{1=2} System.out.println(map.computeIfPresent("1", (k, v) -> null)); //null key存在,开始计算 但是计算结果为null。所以返回null,此处注意remove()掉了对应的key System.out.println(map); //{} }
典型应用场景(以computeIfAbsent为例)
public static void main(String[] args) { Map<String, List<String>> map = new HashMap<>(); List<String> list; // 以前一般这样写,各种判断========================================================== list = map.get("list-1"); if (list == null) { list = new LinkedList<>(); map.put("list-1", list); } list.add("one"); //========================================================== // 使用 computeIfAbsent 可以这样写 这样若已存在,返回值oldValue,若不存在就执行计算,也是返回一个可用的List // 这样对null非常友好,特别好用~~~~~~~~~~ list = map.computeIfAbsent("list-1", k -> new ArrayList<>()); list.add("one"); }
4、最后
集合作为我们使用最为广泛的数据结构,因此了解这里面的一些原理和正确做法,能够使得我们少采坑,希望此文能够帮助到你