一、for
//for循环中删除数据 -- 方法有漏洞
private static void deleteAtFor() {
ArrayList<Integer> array = new ArrayList<>();
array.add(4);
array.add(5);
array.add(5);
array.add(6);
for(int i=0;i<array.size();i++){
if(array.get(i).equals(5)){
array.remove(i);
}
}
System.out.println(array);
}
得到删除后的集合数据是:[4,5,6]
从结果可以看出,有一个等于5的数据没有呗被删除,
这是为什么呢?
当我们深究 for循环在集合中工作原理就可以理解,事实上,当删除一个元素时,后面元素的索引就会前进以为,如删除第一个索引为1的5的时候,此时下一个元素5的索引就变成了1,而此时for循环执行i++,对应索引的位置为2 而新的1索引上的值就不会在读取了,所以集合结果为[4,5,6],要解决这个问题很容易只需要在删除的同时把自增变量i减一,和改变后的索引对应上就好,即把 array.remove(i)改为 array.remove(i--)即可;
如下:
//for循环中删除数据 -- 正确方法
private static void deleteAtFor() {
ArrayList<Integer> array = new ArrayList<>();
array.add(4);
array.add(5);
array.add(5);
array.add(6);
for(int i=0;i<array.size();i++){
if(array.get(i).equals(5)){
array.remove(i--);
}
}
System.out.println(array);
}
结果为[4,6],正确!
二、forEach
//foreach中删除数据
private static void deleteAtForeach() {
ArrayList<Integer> array = new ArrayList<>();
array.add(4);
array.add(5);
array.add(5);
array.add(6);
for (Integer a:array) {
if (4==a){
array.remove(a);
}
}
}
结果如下:
意思是迭代器并发修改异常,出现这个原因是 当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
首先java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator,否则Iterator遍历时会乱,所以直接对list进行删除时,Iterator会抛出ConcurrentModificationException异常
foreach迭代ArrayList时,真的不能删除元素吗,其实删除倒数第二个元素的,具体什么我们就不在这里说明了,大家可以看下这个https://blog.csdn.net/GarfieldEr007/article/details/80260822
三、迭代器
//Iterator迭代器中删除数据
private static void deleteAtIterator() {
ArrayList<Integer> array = new ArrayList<>();
array.add(4);
array.add(5);
array.add(5);
array.add(6);
Iterator<Integer>iterator = array.iterator();
while (iterator.hasNext()){
if (iterator.next().equals(5)){
iterator.remove();
}
}
System.out.println(array);
}
结果是:[4, 6] ;注意点:while里只能出现一次iterator.next(),不然结果会出错。
四、用流的方式
ArrayList<Integer> array = new ArrayList<>();
array.add(4);
array.add(5);
array.add(5);
array.add(6);
array.stream().forEach(integer -> {
if(integer.equals(5)){
array.remove(integer);
}
});
System.out.println(array);
结果:
stream流forEach循环也不能进行删除。具体原因不做深究,哈哈!
五、结论:
在只是遍历情况下,我们四种都可以用,在修改集合的情况下,不能用foreach和stream流forEach;
番外:
如果只是单纯的遍历的话,
从效率角度来看:For Each的效率差,stream流forEach和迭代器的效率也没有很好。for循环最优,因为ArrayList通过数组来实现,数组通过索引来定位的时间复杂度是O(1),1次就能定位到,所以效率非常高;
从代码编写量来看:stream是最简洁的,for循环是量最多的;
但是,我最想推荐的遍历方式是:stream
stream().forEach用的多线程方式,其调用线程池的时候必然会耗费更多的时间。但如果你在循环内要处理的事情很多,或者要循环调用远程接口/数据库的时候,无疑极大的提升了效率。
回顾编程的发展历史,我们不难发现一个规律,那就是先是从最初的C/C++演变到Java/.net,这是编程界的一大进步,因为我们不再关注于指针操作,比如在java中JVM虚拟机已经帮我们完成了相应的操作,由于这一进步,这付出的代价是执行效率会降低,但是带来的好处就在于加快了编程开发的速度。
当编程由Java/.net演变到JavaScript/PHP/Kotlin,这又是编程界的另一大进步,这意味着我们在编写程序时没有必要再关注于数据类型,而该数据类型是由相应的语言在运行时确定,这样,这又一次降低了程序的运行速度,但是相应的又提升了代码编写的效率,因而通过回顾历史我们不难得出如下结论:
在编写代码时,一定要以最简洁为原则,毕竟运行程序的硬件成本会随着时间的推移在不断降低,而程序员的薪资则不会。