首页 > 编程语言 >Java编程:删除 List 元素的三种正确方法

Java编程:删除 List 元素的三种正确方法

时间:2023-11-15 17:06:39浏览次数:30  
标签:元素 java String list 编程 List item Java size

删除 List 中的元素会产生两个问题:

删除元素后 List 的元素数量会发生变化;
对 List 进行删除操作可能会产生并发问题;

我们通过代码示例演示正确的删除逻辑

package com.ips.list;

import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList;

public class ArrayListRemove { public static void main(String[] args) { ArrayList(); list.add("beijing"); list.add("shanghai"); list.add("shanghai"); list.add("guangzhou"); list.add("shenzhen"); list.add("hangzhou"); remove11(list, "shanghai");

}

private static void print(List<String> list){
	for (String item : list) {
		System.out.println("元素值:" + item);
	}
}

/*
 * 错误
 */
public static void remove11(List<String> list, String target){
	int size = list.size();
	for(int i = 0; i < size; i++){
		String item = list.get(i);
		if(target.equals(item)){
			list.remove(item);
		}
	}
	print(list);
}
/*
 * 错误
 */
public static void remove12(List<String> list, String target){
	for(int i = 0; i < list.size(); i++){
		String item = list.get(i);
		if(target.equals(item)){
			list.remove(item);
		}
	}
	print(list);
}
/*
 * 正确
 */
public static void remove13(List<String> list, String target){
	int size = list.size();
	for(int i = size - 1; i >= 0; i--){
		String item = list.get(i);
		if(target.equals(item)){
			list.remove(item);
		}
	}
	print(list);
}
/*
 * 正确
 */
public static void remove14(List<String> list, String target){
	for(int i = list.size() - 1; i >= 0; i--){
		String item = list.get(i);
		if(target.equals(item)){
			list.remove(item);
		}
	}
	print(list);
}

/*
 * 错误
 */
public static void remove21(List<String> list, String target){
	for(String item : list){
		if(target.equals(item)){
			list.remove(item);
		}
	}
	print(list);
}

/*
 * 正确
 */
public static void remove22(ArrayList<String> list, String target) {
	final CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<String>(list);
	for (String item : cowList) {
		if (item.equals(target)) {
			cowList.remove(item);
		}
	}
	print(cowList);
}

/*
 * 错误
 */
public static void remove31(List<String> list, String target){
	Iterator<String> iter = list.iterator();
	while (iter.hasNext()) {
		String item = iter.next();
		if (item.equals(target)) {
			list.remove(item);
		}
	}
	print(list);
}
/*
 * 正确
 */
public static void remove32(List<String> list, String target){
	Iterator<String> iter = list.iterator();
	while (iter.hasNext()) {
		String item = iter.next();
		if (item.equals(target)) {
			iter.remove();
		}
	}
	print(list);
}

}

执行 remove11 方法,出现如下错误:

Exception in thread “main” java.lang.IndexOutOfBoundsException: Index: 5, Size: 5 at java.util.ArrayList.rangeCheck(ArrayList.java:635) at java.util.ArrayList.get(ArrayList.java:411) at com.ips.list.ArrayListRemove.remove11(ArrayListRemove.java:33) at com.ips.list.ArrayListRemove.main(ArrayListRemove.java:17)

由于int size = list.size();提前获取了 List 的大小,for 循环中删除了两个元素,导致出现数组越界问题。

执行 remove12 方法,出现如下错误: 元素值:beijing 元素值:shanghai 元素值:guangzhou 元素值:shenzhen 元素值:hangzhou

字符串“shanghai”没有被删除,该方法解决了数组越界问题,但没有解决彻底删除数据的问题,原因是这样的,跟踪 ArrayList.remove(Object 0) 方法:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

删除元素时执行 else 逻辑,调用了 fastRemove(index) 方法:

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

通过代码我们发现:List 删除元素的逻辑是将目标元素之后的元素往前移一个索引位置,最后一个元素置为 null,同时 size - 1;这也就解释了为什么第二个“shanghai”没有被删除。

执行 remove13 方法,正确:

元素值:beijing

元素值:guangzhou

元素值:shenzhen

元素值:hangzhou

执行 remove14 方法,正确:

元素值:beijing

元素值:guangzhou

元素值:shenzhen

元素值:hangzhou

那么 remove13 与 remove14 有什么区别呢?答案是没有区别,但是 remove11 与 remove12 是有区别的,remove12 中每次for(int i = 0; i < list.size(); i++)执行都会计算 size 值,比较耗性能。

执行 remove21 方法

出现如下错误: Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayListJava编程:删除 List 元素的三种正确方法_删除元素Itr.next(ArrayList.java:831) at com.ips.list.ArrayListRemove.remove21(ArrayListRemove.java:82) at com.ips.list.ArrayListRemove.main(ArrayListRemove.java:17)

产生java.util.ConcurrentModificationException异常。foreach 写法实际上是对的 Iterable、hasNext、next方法的简写。因此我们从List.iterator()着手分析,跟踪iterator()方法,该方法返回了 Itr 迭代器对象。

public Iterator<E> iterator() {
	return new Itr();
}

1 2 3 Itr 类定义代码:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    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];
    }

    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();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

通过代码我们发现 Itr 是 ArrayList 中定义的一个私有内部类,在 next、remove方法中都会调用 checkForComodification 方法,该方法的作用是判断 modCount != expectedModCount是否相等,如果不相等则抛出ConcurrentModificationException异常。每次正常执行 remove 方法后,都会对执行expectedModCount = modCount赋值,保证两个值相等,那么问题基本上已经清晰了,在 foreach 循环中执行 list.remove(item);,对 list 对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,因此抛出了ConcurrentModificationException异常。

执行 remove22 方法

正确:

元素值:beijing

元素值:guangzhou

元素值:shenzhen

元素值:hangzhou

通过 CopyOnWriteArrayList 解决了 List的并发问题。

执行 remove31 方法

出现如下错误: Exception in thread “main” java.util.ConcurrentModificationException at java.util.ArrayListI t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 859 ) a t j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayListItr.checkForComodification(ArrayList.java:859)atjava.util.ArrayListItr.next(ArrayList.java:831) at com.ips.list.ArrayListRemove.remove31(ArrayListRemove.java:109) at com.ips.list.ArrayListRemove.main(ArrayListRemove.java:17)

与执行 remove21 产生的异常一致,问题产生的原因也一致。

执行 remove32 方法

正确:

元素值:beijing

元素值:guangzhou

元素值:shenzhen

元素值:hangzhou

标签:元素,java,String,list,编程,List,item,Java,size
From: https://blog.51cto.com/u_16359267/8398162

相关文章

  • Java开门方法怎么调用关锁方法
    在Java中,如果你想要调用一个方法来锁定门(假设是一个类的方法),你首先需要有一个表示门的类,并且这个类中应该包含开门和关门的方法。这里是一个简单的例子:publicclassDoor{ //开门的方法publicvoidopen(){System.out.println("Doorisopened.");} //关门的方法pu......
  • javascript promise all实现图片顺序加载
    不使用promise时是异步加载,图片加载的顺序不固定<!doctypehtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,user-scalable=no,initial-scale=1......
  • 在Java中实现跨域(Cross-Origin Resource Sharing, CORS)
    在Java中实现跨域(Cross-OriginResourceSharing,CORS)主要涉及到在服务器端设置HTTP响应头,以允许来自不同源的客户端请求。下面是一些常用的方法来实现跨域:1.Servlet过滤器你可以创建一个过滤器(Filter)来添加必要的HTTP头。这种方法适用于所有基于Servlet的应用程序,如纯Servlet......
  • Java流程控制06:While循环详解
     一、while循环publicclassWhileDemo01{publicstaticvoidmain(String[]args){//输出1~100,并且求和inti=1;intsum=0;while(i!=101){System.out.println(i);sum=sum+i;i++......
  • Java中for循环每次都通过list.size、str.length、length()获取数组或者字符串的长度是
    最近看到有同事在使用for循环的时候首先会将数组或者字符串的长度赋值给一个变量;在网上查了一下说是这样可以节约资源的消耗,真实的情况又是如何?让我们看下他们的源码来分析。1.将数组的长度赋值给变量lenList<Integer>list=newArrayList<Integer>();list.add(......
  • java怎么和html结合
    Java和HTML结合通常是在Web开发的背景下进行的。Java可以用于后端服务器编程,而HTML用于前端界面设计。这两者结合主要通过以下几种方式:Servlet和JSP:Servlet:JavaServlet是运行在Web服务器或应用服务器上的程序,它接收来自Web浏览器的请求,并生成响应给浏览器。Servlet通常用......
  • 开发遇到的问题总结---返回的list顺序乱序
    问题描述:从数据库中查询json并转为list,然后做了一些处理之后返回发现顺序变了1.json转换为list对象时:这一步是根据json里面的顺序(不会改变顺序)2.中间将list转为map,然后根据map的keyset获取每个值做一些处理,处理完成增加到返回的list中(这一步会改变顺序,因为map的keyset是无序的,因......
  • java实现的数独游戏
    数独游戏:窗体+逻辑实现类importjavax.swing.*;importjava.awt.*;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;publicclassSudokuGameextendsJFrame{privateJTextField[][]cells;privateint[][]solution;private......
  • Linux_C环境编程:时间日期函数总结
    摘自:https://blog.csdn.net/u010429831/article/details/122722187一、时间日期类型Linux下常用的时间类型有6个:time_t,clock_t,structtimeb,structtimeval,structtimespec,structtm1.1time_t类型time_t是一个长整型,一般用来表示从1970年1月1日0时0分0秒以来的秒数......
  • WSL+ vscode 编程方法记录
    WSL的终端可以安装zsh,使用起来体验感比CMD好太多了,再加上环境配置要方便很多,所以这里记录一下开发的流程和注意事项。这里使用conda控制不同的Python环境,进行日常代码工作。还可以使用Docker实现更加严格和规范的环境配置。Windows图形界面查看WSL中的文件:方法1:资......