1. Collection接口概述
Java集合框架中的Collection接口是所有集合类的根接口,它定义了集合操作的基本契约。作为Java开发者,我们几乎每天都要与Collection接口打交道,无论是处理List、Set还是Queue,本质上都是在操作Collection接口的实现类。
Collection接口位于java.util包中,自Java 1.2引入以来一直是集合框架的核心。它定义了15个方法,这些方法可以分为以下几类:
- 基础操作方法(add, remove, contains等)
- 批量操作方法(addAll, removeAll等)
- 查询操作方法(size, isEmpty等)
- 迭代操作方法(iterator, forEach等)
- 流操作方法(stream, parallelStream)
注意:虽然Map接口也属于集合框架的一部分,但它并不继承自Collection接口,因为Map存储的是键值对而非单个元素。
2. 核心方法详解
2.1 元素操作方法
boolean add(E e)
这是最常用的方法之一,用于向集合中添加元素。返回值表示集合是否因调用而改变(对于不允许重复的Set,添加已存在元素会返回false)。
java复制List<String> list = new ArrayList<>();
boolean added = list.add("Java"); // 返回true
boolean remove(Object o)
移除集合中首次出现的指定元素(如果存在)。注意参数类型是Object而非E,这意味着可以传入任何类型的对象。
java复制List<String> list = new ArrayList<>(Arrays.asList("Java", "Python"));
boolean removed = list.remove("Java"); // 返回true
boolean contains(Object o)
判断集合是否包含指定元素。底层通过equals()方法进行比较,因此自定义对象需要正确实现equals()方法。
2.2 批量操作方法
boolean addAll(Collection<? extends E> c)
将指定集合中的所有元素添加到当前集合中。这是合并两个集合的常用方法。
java复制List<String> list1 = new ArrayList<>(Arrays.asList("A", "B"));
List<String> list2 = new ArrayList<>(Arrays.asList("C", "D"));
list1.addAll(list2); // list1现在包含[A, B, C, D]
boolean removeAll(Collection<?> c)
移除当前集合中所有也包含在指定集合中的元素(差集操作)。
boolean retainAll(Collection<?> c)
仅保留当前集合中那些也包含在指定集合中的元素(交集操作)。
2.3 查询操作方法
int size()
返回集合中的元素数量。这是使用频率最高的方法之一,但需要注意:
- 对于某些并发集合,size()可能是近似值
- 对于某些特殊集合实现,size()可能是高开销操作
boolean isEmpty()
判断集合是否为空。通常比检查size() == 0更高效,因为某些集合实现可以优化这个检查。
2.4 迭代操作方法
Iterator
返回集合的迭代器,这是遍历集合的标准方式。迭代器允许在遍历过程中安全地移除元素。
java复制List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String element = it.next();
if(element.equals("B")) {
it.remove(); // 安全移除当前元素
}
}
default void forEach(Consumer<? super E> action)
Java 8引入的默认方法,提供了一种更简洁的遍历方式。
java复制List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
list.forEach(System.out::println);
3. 高级特性与使用技巧
3.1 Java 8新增方法
default boolean removeIf(Predicate<? super E> filter)
Java 8引入的强大方法,可以基于条件批量移除元素。
java复制List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.removeIf(n -> n % 2 == 0); // 移除所有偶数
default Spliterator
返回可分割的迭代器,主要用于并行流处理。
3.2 集合与数组转换
Object[] toArray()
将集合转换为数组。注意返回的是Object数组,通常不够实用。
更实用的数组转换方法,可以指定数组类型。
java复制List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
String[] array = list.toArray(new String[0]);
3.3 不可变集合
从Java 9开始,可以使用List.of(), Set.of()等工厂方法创建不可变集合:
java复制Set<String> immutableSet = Set.of("A", "B", "C");
这些集合不允许null元素,且尝试修改会抛出UnsupportedOperationException。
4. 性能考量与最佳实践
4.1 方法时间复杂度
不同集合实现的相同方法可能有完全不同的时间复杂度:
| 方法 | ArrayList | LinkedList | HashSet |
|---|---|---|---|
| add() | O(1) 摊还 | O(1) | O(1) |
| remove() | O(n) | O(n) | O(1) |
| contains() | O(n) | O(n) | O(1) |
| get(int) | O(1) | O(n) | N/A |
4.2 常见陷阱
-
并发修改异常
使用迭代器遍历时直接调用集合的add/remove方法会导致ConcurrentModificationException:java复制List<String> list = new ArrayList<>(Arrays.asList("A", "B")); for(String s : list) { if(s.equals("A")) { list.remove(s); // 抛出ConcurrentModificationException } } -
equals和hashCode一致性
当自定义对象作为集合元素时,必须确保:- 如果a.equals(b),那么a.hashCode()必须等于b.hashCode()
- equals方法应该满足自反性、对称性、传递性和一致性
-
初始容量设置
对于ArrayList和HashSet等基于数组的实现,合理设置初始容量可以避免多次扩容:java复制// 预计有1000个元素,负载因子0.75 Set<String> set = new HashSet<>(1333); // 1000/0.75 ≈ 1333
5. 实际应用场景
5.1 数据过滤
利用removeIf和流API可以轻松实现复杂过滤:
java复制List<Employee> employees = ...;
// 过滤掉薪资低于5000且年龄大于40的员工
employees.removeIf(e -> e.getSalary() < 5000 && e.getAge() > 40);
5.2 集合运算
通过批量操作方法实现集合运算:
java复制// 求两个列表的交集
List<String> list1 = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list2 = new ArrayList<>(Arrays.asList("B", "C", "D"));
list1.retainAll(list2); // list1现在只包含[B, C]
5.3 数据转换
结合流API实现数据转换:
java复制List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
6. 常见问题排查
-
UnsupportedOperationException
当尝试修改不可变集合(如Arrays.asList()返回的集合)时会抛出此异常。解决方案:java复制List<String> fixedList = Arrays.asList("A", "B"); List<String> modifiableList = new ArrayList<>(fixedList); // 创建可修改副本 -
NullPointerException
某些集合实现不允许null元素(如ConcurrentHashMap)。在添加元素前应做null检查。 -
内存泄漏
当使用HashSet/HashMap时,如果修改了作为键的对象的hashCode相关字段,会导致元素"丢失":java复制Set<Employee> set = new HashSet<>(); Employee emp = new Employee("John"); set.add(emp); emp.setName("Alice"); // 修改了影响hashCode的字段 set.contains(emp); // 可能返回false -
性能问题
对于大型集合,错误的遍历方式可能导致性能问题:java复制// LinkedList的糟糕遍历方式 - O(n²) LinkedList<String> list = ...; for(int i=0; i<list.size(); i++) { String s = list.get(i); // LinkedList的get(i)是O(n)操作 }应该使用迭代器或增强for循环:
java复制for(String s : list) { ... } // 使用迭代器,O(n)
7. 扩展知识
7.1 Java集合框架演进
- Java 1.2:引入集合框架(Collection Framework)
- Java 5:增加泛型支持
- Java 8:引入流API和默认方法
- Java 9:增加不可变集合工厂方法
- Java 10:增加copyOf方法创建不可变集合
- Java 16:增强的toList()等收集器方法
7.2 第三方集合库
除了标准库,还有一些优秀的第三方集合库:
-
Guava(Google Collections)
- 提供Multimap、Multiset等高级集合
- 丰富的不可变集合实现
- 各种实用工具类
-
Eclipse Collections
- 内存高效的集合实现
- 丰富的原始类型集合(IntList等)
- 流畅的API设计
-
FastUtil
- 针对原始类型优化的集合
- 内存占用小,性能高
7.3 并发集合
对于多线程环境,应该使用java.util.concurrent包中的线程安全集合:
- CopyOnWriteArrayList:适合读多写少的场景
- ConcurrentHashMap:高性能并发Map实现
- ConcurrentSkipListSet:有序的并发Set
这些集合通过不同的并发策略(如写时复制、分段锁等)实现了线程安全。
8. 面试常见问题
-
Collection和Collections的区别?
- Collection是集合接口
- Collections是工具类,包含操作集合的静态方法
-
fail-fast和fail-safe迭代器的区别?
- fail-fast(如ArrayList)在检测到并发修改时立即抛出异常
- fail-safe(如ConcurrentHashMap)允许在迭代时修改集合
-
ArrayList和LinkedList的区别?
- ArrayList基于动态数组,随机访问快,插入删除慢
- LinkedList基于双向链表,随机访问慢,插入删除快
-
HashSet如何检查重复?
先比较hashCode(),如果相同再比较equals() -
如何实现一个不可变集合?
- Java 9+:使用List.of()等工厂方法
- 旧版本:使用Collections.unmodifiableXXX()包装
- 完全不可变需要深度防御性拷贝
9. 性能优化建议
-
选择合适的集合类型
- 需要快速查找:HashSet/HashMap
- 需要保持插入顺序:LinkedHashSet/LinkedHashMap
- 需要排序:TreeSet/TreeMap
- 频繁插入删除:LinkedList
-
预分配容量
对于ArrayList、HashMap等,如果能预估大小,应该预先设置合适的初始容量:java复制// 预计存储1000个元素 List<String> list = new ArrayList<>(1000); Map<String, Integer> map = new HashMap<>(1333); // 1000/0.75 -
使用原始类型集合
对于基本数据类型,考虑使用第三方库(如FastUtil)的专门集合,避免自动装箱开销。 -
并行处理大型集合
对于CPU密集型操作,可以考虑使用并行流:java复制
List<Data> results = largeList.parallelStream() .filter(...) .map(...) .collect(Collectors.toList()); -
缓存频繁访问的结果
对于计算开销大的操作(如集合的hashCode),考虑缓存结果:java复制class ExpensiveObject { private int cachedHash; private boolean hashCalculated; @Override public int hashCode() { if(!hashCalculated) { cachedHash = computeHash(); hashCalculated = true; } return cachedHash; } }
10. 实际案例:实现一个多功能集合工具类
下面是一个综合运用Collection方法的实用工具类示例:
java复制public class CollectionUtils {
/**
* 合并多个集合(去重)
*/
@SafeVarargs
public static <T> Set<T> mergeUnique(Collection<T>... collections) {
Set<T> result = new HashSet<>();
for(Collection<T> coll : collections) {
result.addAll(coll);
}
return result;
}
/**
* 过滤并转换集合
*/
public static <T, R> List<R> filterAndTransform(
Collection<T> collection,
Predicate<? super T> filter,
Function<? super T, ? extends R> mapper) {
return collection.stream()
.filter(filter)
.map(mapper)
.collect(Collectors.toList());
}
/**
* 分区集合(类似Guava的Lists.partition)
*/
public static <T> List<List<T>> partition(Collection<T> collection, int size) {
if(size <= 0) throw new IllegalArgumentException("分区大小必须为正数");
List<T> list = new ArrayList<>(collection);
List<List<T>> result = new ArrayList<>();
for(int i=0; i<list.size(); i+=size) {
int end = Math.min(i+size, list.size());
result.add(list.subList(i, end));
}
return result;
}
/**
* 安全的空集合检查
*/
public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.isEmpty();
}
}
这个工具类展示了如何组合使用Collection接口的各种方法来解决实际问题。其中:
- mergeUnique利用了addAll的去重特性
- filterAndTransform结合了流式处理
- partition展示了子列表操作
- isEmpty提供了健壮的空检查
在实际项目中,这样的工具类可以显著提高代码的可读性和重用性。
