今天系统练习了Collection接口的核心方法,从contains判断到iterator遍历,每个方法都藏着一些容易忽略的细节。让我通过代码记录下这些重要发现。

1. contains方法:equals()的隐形调用

@Test
public void testContains() {
    Collection coll = new ArrayList();
    coll.add(new Person("李四", 18));
    
    // 关键:contains会调用对象的equals()方法
    boolean contains = coll.contains(new Person("李四", 18));
    System.out.println("contains结果: " + contains); // true
    
    // 前提:Person类必须正确重写equals()方法
    // 否则比较的是对象地址,返回false
}

重要发现contains()不是简单的==比较,它会自动调用元素的equals()方法。这意味着自定义类必须正确重写equals()和hashCode()。

2. containsAll:批量包含检查

@Test  
public void testContainsAll() {
    Collection coll = new ArrayList();
    coll.add(666);
    coll.add(888);
    coll.add(true);
    coll.add(new String("张三"));
    
    // 检查另一个集合的所有元素是否都在当前集合中
    Collection coll2 = Arrays.asList(true, new String("张三"));
    boolean containsAll = coll.containsAll(coll2);
    System.out.println("containsAll结果: " + containsAll); // true
    
    // 注意:这也是基于equals()比较的
}

3. remove方法:不仅仅是删除

@Test
public void testRemove() {
    Collection coll = new ArrayList();
    coll.add(666);
    coll.add(888);
    coll.add(new String("张三"));
    coll.add(new Person("李四", 18));
    
    // 删除元素,同样依赖equals()
    boolean removed = coll.remove(new String("张三"));
    System.out.println("是否删除成功: " + removed); // true
    System.out.println("删除后集合: " + coll);
    
    // 如果元素不存在,remove()返回false
    boolean notExist = coll.remove("不存在的元素");
    System.out.println("删除不存在的元素: " + notExist); // false
}

4. removeAll vs retainAll:集合的差集与交集

@Test
public void testRemoveAllAndRetainAll() {
    Collection coll = new ArrayList();
    coll.add(666);
    coll.add(888);
    coll.add(true);
    coll.add(new Person("李四", 18));
    
    System.out.println("原始集合: " + coll);
    
    // removeAll: 移除交集部分
    Collection toRemove = Arrays.asList(666, new Person("李四", 18));
    coll.removeAll(toRemove);
    System.out.println("removeAll后: " + coll); // [888, true]
    
    // retainAll: 保留交集部分
    Collection toRetain = Arrays.asList(987, 666, true);
    coll.retainAll(toRetain);
    System.out.println("retainAll后: " + coll); // [true]
}

对比记忆

  • removeAll():删掉两集合都有的元素(差集)
  • retainAll():只保留两集合都有的元素(交集)

5. equals方法:顺序敏感的相等比较

@Test
public void testEquals() {
    Collection coll1 = new ArrayList();
    coll1.add(true);
    coll1.add(888);
    
    Collection coll2 = new ArrayList();
    coll2.add(true);
    coll2.add(888);
    
    // 对于List,equals要求元素相同且顺序一致
    System.out.println("顺序相同: " + coll1.equals(coll2)); // true
    
    Collection coll3 = new ArrayList();
    coll3.add(888);
    coll3.add(true);
    
    System.out.println("顺序不同: " + coll1.equals(coll3)); // false
    
    // 注意:Set的equals不要求顺序一致
}

6. toArray与asList:集合与数组的转换

@Test
public void testArrayConversion() {
    // 集合 -> 数组
    Collection coll = Arrays.asList("A", "B", "C");
    Object[] array = coll.toArray();
    System.out.println("集合转数组: " + Arrays.toString(array));
    
    // 数组 -> 集合(注意陷阱!)
    System.out.println("\n--- 数组转集合的陷阱 ---");
    
    // 基本类型数组:整个数组被视为一个元素
    List<int[]> wrongList = Arrays.asList(new int[]{1, 2, 3});
    System.out.println("int[]转List的大小: " + wrongList.size()); // 1
    
    // 包装类型数组:每个元素被单独添加
    List<Integer> rightList = Arrays.asList(new Integer[]{1, 2, 3});
    System.out.println("Integer[]转List的大小: " + rightList.size()); // 3
    
    // 注意:asList()返回的是固定大小的List,不能增删!
    // rightList.add(4); // 会抛UnsupportedOperationException
}

关键提醒Arrays.asList()对于基本类型数组和对象数组的行为不同!

7. hashCode方法:集合的哈希值计算

@Test
public void testHashCode() {
    Collection coll1 = new ArrayList();
    coll1.add(true);
    coll1.add(888);
    
    Collection coll2 = new ArrayList();
    coll2.add(true);
    coll2.add(888);
    
    // 两个相等的集合,hashCode必须相等
    System.out.println("coll1 hashCode: " + coll1.hashCode());
    System.out.println("coll2 hashCode: " + coll2.hashCode());
    System.out.println("hashCode是否相等: " + 
                      (coll1.hashCode() == coll2.hashCode())); // true
}

8. iterator方法:集合遍历的正确姿势

@Test
public void testIterator() {
    Collection<String> coll = new ArrayList<>();
    coll.add("Java");
    coll.add("Python");
    coll.add("Go");
    coll.add("JavaScript");
    
    System.out.println("--- 使用iterator遍历 ---");
    // 获取迭代器
    Iterator<String> iterator = coll.iterator();
    
    // 标准遍历方式
    while (iterator.hasNext()) {
        String language = iterator.next();
        System.out.println("编程语言: " + language);
        
        // 可以在遍历时安全删除
        if ("Go".equals(language)) {
            iterator.remove(); // 安全删除当前元素
            System.out.println("已删除: Go");
        }
    }
    
    System.out.println("\n--- 遍历后集合内容 ---");
    System.out.println(coll); // [Java, Python, JavaScript]
    
    System.out.println("\n--- 常见错误示例 ---");
    // 错误1:重复调用next()跳过元素
    Iterator<String> it1 = coll.iterator();
    while (it1.hasNext()) {
        System.out.println(it1.next()); // 正确
        // System.out.println(it1.next()); // 错误!会跳过一个元素
    }
    
    // 错误2:遍历时用集合的remove()方法
    Iterator<String> it2 = coll.iterator();
    while (it2.hasNext()) {
        String lang = it2.next();
        if ("Java".equals(lang)) {
            // coll.remove(lang); // 错误!会抛ConcurrentModificationException
            it2.remove(); // 正确!使用迭代器的remove()
        }
    }
    
    // 增强for循环底层也是iterator
    System.out.println("\n--- 增强for循环 ---");
    for (String lang : coll) {
        System.out.println("语言: " + lang);
        // 这里也不能直接调用coll.remove()
    }
}

今日学习总结

核心收获:

  1. equals()是集合操作的基石

    • contains、remove、containsAll等都依赖equals()
    • 自定义类必须正确重写equals()和hashCode()
  2. 集合运算的三种模式

    • 单个操作:add、remove、contains
    • 批量操作:addAll、removeAll、retainAll、containsAll
    • 转换操作:toArray、asList
  3. 数组与集合转换的陷阱

    • 基本类型数组用asList()会得到单个元素
    • asList()返回的是固定大小的List
  4. 迭代器的正确使用

    • 遍历时删除必须用iterator.remove()
    • 不能混用集合的remove()方法
    • 每个迭代器只能使用一次

实际应用建议:

// 1. 判断元素是否存在
if (collection.contains(target)) {
    // 确保元素类重写了equals()
}

// 2. 批量删除
collection.removeAll(toRemove); // 比循环删除高效

// 3. 安全遍历删除
Iterator<T> it = collection.iterator();
while (it.hasNext()) {
    T item = it.next();
    if (shouldRemove(item)) {
        it.remove(); // 唯一安全的方式
    }
}

// 4. 集合比较
if (list1.equals(list2)) {
    // List要求顺序一致
}
if (set1.equals(set2)) {  
    // Set不要求顺序一致
}

性能注意事项:

  • contains()在ArrayList中是O(n),在HashSet中是O(1)
  • 频繁的contains检查考虑使用Set
  • 大集合的遍历考虑使用Iterator而非get(index)

练习感悟:集合的API设计体现了Java的面向对象思想。每个方法都不是孤立的,而是通过equals()、hashCode()等机制相互关联。理解这些内在联系,才能真正掌握集合框架的精髓。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐