黑马JAVA+AI 加强03-集合-Collection-List和Set集合-迭代器(Iterator)遍历-并发修改异常

1.集合

1.1 Collection和Map集合

1.2List集合和Set集合

  • List 家族集合的核心是有序且可重复,Set 家族集合的核心是不可重复,有序性则因具体实现类而异。
  1. List 家族集合特点
  • List 集合是元素有序、可重复的集合,用户可以通过索引(类似数组下标)精确控制元素的插入位 置和访问顺序。
  • 有序性:元素的存入顺序和取出顺序完全一致,支持通过索引(get(int index))直接访问指定位置的元素。
  • 可重复性:集合中允许存在多个equals()方法返回true的元素,包括多个null值。
  • 常用实现类:
    • ArrayList:基于动态数组实现,查询效率高,增删效率低(尤其中间位置)。
    • LinkedList:基于双向链表实现,增删效率高(尤其首尾),查询效率低。
    • Vector:线程安全的动态数组,性能较差,已逐渐被ArrayList替代。
  1. Set 家族集合特点
  • Set 集合的核心是元素不可重复,即不允许存在两个equals()方法返回true的元素,有序性需看具体实现。
  • 不可重复性:添加元素时,会通过元素的hashCode()和equals()方法判断是否重复,重复元素会被自动过滤,最多允许一个null值。
  • 有序性差异:
    • 无序实现类:HashSet,基于哈希表实现,元素存储顺序与存入顺序无关,查找效率高。
    • 有序实现类:TreeSet,基于红黑树实现,会按元素的自然顺序(或自定义比较器)对元素进行排序;LinkedHashSet,基于哈希表 + 链表实现,能保证元素的存入顺序与取出顺序一致。
    • 常用实现类:HashSet(追求效率)、TreeSet(需要排序)、LinkedHashSet(兼顾顺序与效率)。

      2.Collection集合
java 复制代码
public class CollectionDemo {
    public static void main(String[] args) {
        // 创建Collection集合(使用ArrayList实现类,因为Collection是接口不能直接实例化)
        Collection<String> coll = new ArrayList<>();

        // 1. 添加元素:add(E e)、addAll(Collection<? extends E> c)
        coll.add("Java");
        coll.add("Python");
        coll.add("C++");
        System.out.println("添加元素后:" + coll); // [Java, Python, C++]

        // 创建另一个集合,用于测试addAll
        Collection<String> coll2 = new ArrayList<>();
        coll2.add("JavaScript");
        coll2.add("Go");
        coll.addAll(coll2); // 将coll2的所有元素添加到coll中
        System.out.println("添加coll2后:" + coll); // [Java, Python, C++, JavaScript, Go]


        // 2. 判断元素:contains(Object o)、containsAll(Collection<?> c)、isEmpty()
        boolean hasJava = coll.contains("Java");
        System.out.println("是否包含Java?" + hasJava); // true

        boolean hasAll = coll.containsAll(coll2); // 判断coll是否包含coll2的所有元素
        System.out.println("是否包含coll2的所有元素?" + hasAll); // true

        boolean isEmpty = coll.isEmpty();
        System.out.println("集合是否为空?" + isEmpty); // false

        // 3. 获取元素数量:size()
        int size = coll.size();
        System.out.println("集合元素个数:" + size); // 5

        // 4. 删除元素:remove(Object o)、removeAll(Collection<?> c)、clear()
        boolean removed = coll.remove("C++"); // 删除指定元素
        System.out.println("删除C++是否成功?" + removed); // true
        System.out.println("删除后:" + coll); // [Java, Python, JavaScript, Go]

        boolean removedAll = coll.removeAll(coll2); // 删除coll中与coll2的交集元素
        System.out.println("删除coll2交集元素是否成功?" + removedAll); // true
        System.out.println("删除交集后:" + coll); // [Java, Python]

        // 5. 遍历元素:iterator()(迭代器遍历)
        System.out.print("迭代器遍历元素:");
        Iterator<String> it = coll.iterator();
        while (it.hasNext()) { // 判断是否有下一个元素
            String elem = it.next(); // 获取下一个元素
            System.out.print(elem + " ");
        }
        System.out.println(); // 输出:Java Python

        // 6. 转换为数组:toArray()
        Object[] array = coll.toArray();
        System.out.print("转换为数组后:");
        for (Object obj : array) {
            System.out.print(obj + " ");
        }
        System.out.println(); // 输出:Java Python

        // 7. 清空集合:clear()
        coll.clear();
        System.out.println("清空后集合:" + coll); // []
        System.out.println("清空后是否为空?" + coll.isEmpty()); // true
    }
}

代码说明:
1.方法覆盖:包含了 Collection 接口的所有通用方法
(add、addAll、remove、removeAll、contains、containsAll、size、isEmpty、iterator、toArray、clear)。
2.实现类选择:用 ArrayList 实例化 Collection,因为接口不能直接创建对象,需通过具体实现类
(ArrayList、HashSet 等都可,此处以常用的 ArrayList 为例)。
3.核心逻辑:通过逐步操作展示集合从创建、添加元素、判断元素、删除元素到遍历、清空的完整流程,
直观体现 Collection 方法的作用。

3.迭代器

3.1 迭代器(Iterator)遍历(最基础、通用的方式)

Iterator 是 Collection 接口提供的通用遍历工具,所有实现类都支持,适用于所有 Collection 集合(包括 Set,因为 Set 无索引)。

核心方法:

hasNext():判断是否有下一个元素。

next():获取下一个元素(注意:必须先调用 hasNext() 判断,否则可能抛出 NoSuchElementException)。

remove():删除当前迭代的元素(可选操作,需谨慎使用)。

  1. 迭代器(Iterator)遍历(最基础、通用的方式)
  • Iterator 是 Collection 接口提供的通用遍历工具,所有实现类都支持,适用于所有 Collection 集合(包括 Set,因为 Set 无索引)。
    核心方法:
    • hasNext():判断是否有下一个元素。
    • next():获取下一个元素(注意:必须先调用 hasNext() 判断,否则可能抛出 NoSuchElementException)。
    • remove():删除当前迭代的元素(可选操作,需谨慎使用)。
java 复制代码
public class CollectionTraversal {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("Java");
        coll.add("Python");
        coll.add("C++");

        // 获取迭代器对象
        Iterator<String> it = coll.iterator();
        // 遍历
        while (it.hasNext()) {
            String elem = it.next(); // 获取元素
            System.out.println(elem);
        }
    }
}

3.2 增强 for 循环(foreach,JDK 5+)

  • 简化版的迭代器遍历,语法更简洁,适用于只需要 "读取" 元素,不需要 "修改集合结构"(如增删元素)的场景。
java 复制代码
// 承接上面的elems集合
for (String elem : elems) {
    System.out.println(elem);
}

3.3 Lambda表达式

  • 注意:增强 for 循环底层也是迭代器,若在循环中使用集合的 remove() 方法修改集合结构,会抛出 ConcurrentModificationException(并发修改异常)。如需删除元素,需用迭代器的 remove() 方法。

3.4 普通 for 循环(仅适用于 List 集合)

  • 利用 List 集合的索引(get(int index) 方法)遍历,仅支持 List 及其实现类(如 ArrayList、LinkedList),Set 集合无索引,不能使用。
java 复制代码
public class ListForLoop {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Python");
        list.add("C++");

        // 普通for循环(依赖索引)
        for (int i = 0; i < list.size(); i++) {
            String elem = list.get(i);
            System.out.println(elem);
        }
    }
}

3.5 JDK 8+ 流式遍历(forEach + Lambda)

  • 基于 Stream API 的遍历方式,语法简洁,支持函数式编程,可结合过滤、映射等操作。
java 复制代码
// 承接上面的coll集合
coll.forEach(elem -> System.out.println(elem));

// 简化写法(方法引用)
coll.forEach(System.out::println);

4.并发修改异常(ConcurrentModificationException)

  • 是一种常见错误,通常发生在 "遍历集合的同时修改集合结构(增删元素)" 的场景中。
java 复制代码
public class CollectionTraversalTest6 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Java入门");
        list.add("宁夏枸杞");
        list.add("黑枸杞");
        list.add("人字拖");
        list.add("特级枸杞");
        list.add("枸杞子");
        list.add("西洋参");

        // 需求1:删除全部枸杞
        for (int i = 0; i < list.size(); i++) {
            String name = list.get(i);
            if (name.contains("枸杞")) {
                list.remove(name);
                i--; // 删除后索引回退,保证后续元素不被跳过
            }
        }
        System.out.println(list);
    }
}
输出结果:[Java 入门,黑枸杞,人字拖,枸杞子,西洋参]
删除了宁夏枸杞后,跳过了黑枸杞,删除了特技枸杞后跳过了枸杞子,这是怎么回事?
原因是:index索引[1]到了宁夏枸杞这个位置,把宁夏枸杞删除后,黑枸杞自动跑到了索引[2],
而iterator.hasNext()会自动往后走1个索引,故index接下来走到了[2]对应的是人字拖,跳过了黑枸杞.

解决方案↓
通过迭代器方式实现(更符合 "避免并发修改异常" 的规范场景)
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String name = iterator.next();
            if (name.contains("枸杞")) {
                iterator.remove(); // 用迭代器的remove方法,避免索引问题
            }
        }
        System.out.println(list);
执行修正后的代码,输出为:[Java入门, 人字拖, 西洋参](所有含 "枸杞" 的元素均被删除)。
  • 产生原因
    Java 集合(如ArrayList、HashMap等)的迭代器(Iterator)采用了快速失败(fail-fast)机制:迭代器在创建时会记录集合的「修改次数」,遍历过程中会实时检查这个次数。如果发现集合被意外修改(如通过集合自身的add()、remove()方法,而非迭代器的remove()),就会立即抛出ConcurrentModificationException,避免迭代过程中出现数据不一致。

4.1单线程中边遍历边修改集合

java 复制代码
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");

// 遍历集合
for (String s : list) {  // 增强for循环底层依赖Iterator
    if (s.equals("A")) {
        list.remove(s);  // 直接通过集合修改,触发异常
    }
}
  • 多线程并发操作集合一个线程在遍历集合,另一个线程同时修改集合,也可能触发该异常。

4.2解决方案

  • 使用迭代器自身的remove()方法迭代器的remove()会同步更新「修改次数」,避免异常:
java 复制代码
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if (s.equals("A")) {
        iterator.remove();  // 正确:通过迭代器修改
    }
}
注意:迭代器的remove()只能在next()之后调用,且每次next()后只能调用一次。
  • 使用普通 for 循环(基于索引)适用于有序集合(如ArrayList),通过索引遍历并修改:
java 复制代码
for (int i = 0; i < list.size(); i++) {
    if (list.get(i).equals("A")) {
        list.remove(i);
        i--;  // 删除后索引需回退,避免跳过元素
    }
}

5.倒着遍历也能解决问题!!

相关推荐
cj6341181508 小时前
DBeaver连接本地MySQL、创建数据库表的基础操作
java·后端
书院门前细致的苹果8 小时前
深入理解 Java 多线程与线程池 —— 从原理到实战
java·开发语言
大G的笔记本8 小时前
用 Redis 的 List 存储库存队列,并通过 LPOP 原子性出队来保证并发安全案例
java·数据库·redis·缓存
太过平凡的小蚂蚁9 小时前
适配器模式:让不兼容的接口协同工作
java·前端·javascript
ljh_learn_from_base9 小时前
【spring boot 使用apache poi 生成和处理word 文档】
java·spring boot·word·apache
数字芯片实验室9 小时前
流片可以失败,但人心的账本不能亏空
java·开发语言
华仔啊9 小时前
为什么你的 @Transactional 不生效?一文搞懂 Spring 事务机制
java·后端
Lacrimosa&L9 小时前
OS_3 Memory、4 File、5 IO
java
爱学的小码9 小时前
JavaEE——多线程1(超详细版)
java·java-ee