List移除元素的四种方式
List 移除某个元素
四种方式:
- 方式一,使用 Iterator ,顺序向下,如果找到元素,则使用 remove 方法进行移除。
- 方式二,倒序遍历 List ,如果找到元素,则使用 remove 方法进行移除。
- 方式三,正序遍历 List ,如果找到元素,则使用 remove 方法进行移除,然后进行索引 “自减”。
- 方式四,使用jdk1.8新增的Stream流操作
1.Iterator 迭代器
@Test
public void fun9(){
List<String> list = new ArrayList<>();
list.add("赵云");
list.add("黄忠");
list.add("马超");
list.add("关羽");
list.add("张飞");
// 获取迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
if("关羽".equals(str)){
it.remove();
}
}
System.out.println(list);
}
2.倒序遍历
@Test
public void fun10(){
List<String> list = new ArrayList<>();
list.add("赵云");
list.add("黄忠");
list.add("马超");
list.add("关羽");
list.add("张飞");
for (int i = list.size() - 1; i > 0; i--) {
if("关羽".equals(list.get(i))){
list.remove(i);
}
}
System.out.println(list);
}
3.正序遍历
@Test
public void fun11(){
List<String> list = new ArrayList<>();
list.add("赵云");
list.add("黄忠");
list.add("马超");
list.add("关羽");
list.add("张飞");
for (int i = 0; i < list.size(); i++) {
if("关羽".equals(list.get(i))){
list.remove(i);
i--;
}
}
System.out.println(list);
}
4.Stream流操作(JDK 1.8 +)
@Test
public void fun8(){
List<String> list = new ArrayList<>();
list.add("赵云");
list.add("黄忠");
list.add("马超");
list.add("关羽");
list.add("张飞");
// 筛选出不是“关羽” 的集合
list = list.stream().filter(e -> !"关羽".equals(e)).collect(Collectors.toList());
System.out.println("method4|list=" + list);
}
问题:
1.为什么不能使用forEach
@Test
public void fun5(){
List<String> list = new ArrayList<>();
list.add("赵云");
list.add("黄忠");
list.add("马超");
list.add("关羽");
list.add("张飞");
for (String str :list) {
if ("张飞".equals(str)){
list.remove(str);
}
}
System.out.println(list);
}
原因:
foreach方式遍历元素的时候,是生成iterator,然后使用iterator遍历。在生成iterator的时候,会保存一个expectedModCount参数,这个是生成iterator的时候List中修改元素的次数。如果你在遍历过程中删除元素,List中modCount就会变化,如果这个modCount和exceptedModCount不一致,就会抛出异常。这个是为了安全的考虑。如果使用iterator遍历过程中,使用List修改了元素,可能会出现不正常的现象。如果使用iterator的remove方法则会正常,因为iterator的remove方法会在内部调用List的remove方法,但是会修改excepedModCount的值,因此会正常运行。
2.为什么forEach 删除倒数第二元素不会出现异常
@Test
public void fun12() {
List<String> list = new ArrayList<>();
list.add("赵云");
list.add("黄忠");
list.add("马超");
list.add("关羽");
list.add("张飞");
for (String str:list) {
if("关羽".equals(str)){
list.remove(str);
}
System.out.println(str);
}
}
仔细观察发现集合最后一个元素(“张飞”)并没有被遍历出来,因为当我们移除倒数第二个元素(“关羽”)时 cursor(游标)为 4 ,list 中size 属性的值会发生变化(5 - 1 = 4)变为 4,所以下面代码返回 false ,也就不会继续向下遍历。这也是能够正常执行的原因,因为如果继续遍历就会出现问题1 中的情况,在进行checkForComodification() 时,因为 modCount 发生了变化,而expectedModCount 并没有发生变化,所以会出现 ConcurrentModificationException异常。
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
3 普通正序 for 循环为什么要 i –
因为遍历过程中进行remove 操作时,该位置后面的元素会挤到前面来,这时候会发生一种情况就是原来元素的位置会被他后面的元素取代,而该位置已经遍历过了,所以该元素不会背遍历。 所以要进行 i-- 操作从该位置重新遍历。
@Test
public void fun11(){
List<String> list = new ArrayList<>();
list.add("赵云");
list.add("黄忠");
list.add("马超");
list.add("关羽");
list.add("张飞");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
if("关羽".equals(list.get(i))){
list.remove(i);
}
}
System.out.println(list);
}
就是下面的情况 “张飞” 不见了…
4 为什么倒序for 循环可以
当我们倒序遍历元素的时候,无论删除元素之后的元素怎么移动,之前的元素对应的索引(index)是不会发生变化的,所以在删除元素的时候不会发生问题。