1. Java集合框架概述
Java集合框架(Java Collections Framework,JCF)是Java语言中最重要的基础库之一,它为开发者提供了一套标准化的接口和实现,用于存储和操作对象集合。这个框架的设计体现了几个核心思想:
-
接口与实现分离:框架定义了一系列接口,如Collection、List、Set等,同时提供了多种具体实现类,如ArrayList、HashSet等。这种设计允许开发者基于接口编程,而不必关心底层具体实现。
-
算法复用:通过将通用算法(如排序、搜索)与数据结构分离,实现了代码的高度复用。例如Collections工具类提供了大量静态方法,可以操作各种集合。
-
类型安全:从JDK 1.5开始引入泛型,使得集合可以在编译期进行类型检查,避免了运行时的ClassCastException。
提示:理解集合框架的设计哲学对于正确使用集合类至关重要。接口定义行为契约,实现类提供具体功能,这种分离让代码更加灵活和可维护。
2. Collection接口体系
2.1 Collection根接口
Collection接口是所有单列集合的顶层接口,定义了集合操作的基本契约:
java复制public interface Collection<E> extends Iterable<E> {
// 基本操作
int size();
boolean isEmpty();
boolean contains(Object o);
boolean add(E e);
boolean remove(Object o);
// 批量操作
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
void clear();
// 数组转换
Object[] toArray();
<T> T[] toArray(T[] a);
// JDK 8+ 默认方法
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
}
关键点解析:
size()和isEmpty():获取集合大小和判断是否为空。注意size()返回的是int类型,最大值为Integer.MAX_VALUE。add()和remove():添加和移除元素,返回操作是否成功。注意这些方法可能抛出UnsupportedOperationException。toArray():将集合转换为数组。推荐使用带泛型参数的版本toArray(T[] a),可以避免类型转换。
2.2 List接口
List接口扩展了Collection,表示有序集合(序列),允许重复元素:
java复制public interface List<E> extends Collection<E> {
// 位置访问操作
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
// 搜索操作
int indexOf(Object o);
int lastIndexOf(Object o);
// 列表迭代器
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
// 子列表视图
List<E> subList(int fromIndex, int toIndex);
}
实现类对比:
| 特性 | ArrayList | LinkedList | Vector |
|---|---|---|---|
| 数据结构 | 动态数组 | 双向链表 | 动态数组 |
| 线程安全 | 否 | 否 | 是 |
| 随机访问 | O(1) | O(n) | O(1) |
| 插入删除 | O(n) | O(1) | O(n) |
| 扩容机制 | 1.5倍 | 无需扩容 | 2倍 |
使用建议:
- 大多数情况下优先使用ArrayList,因为它的随机访问性能最好。
- 当需要频繁在列表中间插入/删除元素时,考虑使用LinkedList。
- Vector由于同步开销大,已经不推荐使用,可以用Collections.synchronizedList包装ArrayList。
2.3 Set接口
Set接口表示不包含重复元素的集合:
java复制public interface Set<E> extends Collection<E> {
// 继承Collection的所有方法
// 添加了关于数学集合操作的约定
}
主要实现类:
- HashSet:基于HashMap实现,依赖hashCode()和equals()方法判断元素是否相同:
java复制Set<String> set = new HashSet<>();
set.add("a"); // 实际调用HashMap.put("a", PRESENT)
- LinkedHashSet:继承HashSet,同时维护元素的插入顺序:
java复制Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("b");
linkedSet.add("a");
// 遍历顺序保证是b, a
- TreeSet:基于TreeMap实现,元素按照自然顺序或Comparator排序:
java复制Set<String> treeSet = new TreeSet<>();
treeSet.add("b");
treeSet.add("a");
// 遍历顺序是a, b
性能比较:
- HashSet:O(1)时间复杂度的基本操作
- LinkedHashSet:比HashSet略慢,因为要维护链表
- TreeSet:O(log n)时间复杂度的基本操作
2.4 Queue接口
Queue表示先进先出(FIFO)的队列:
java复制public interface Queue<E> extends Collection<E> {
// 插入操作
boolean offer(E e); // 推荐使用,失败返回false
boolean add(E e); // 可能抛出异常
// 移除操作
E poll(); // 返回并移除队首
E remove(); // 可能抛出异常
// 检查操作
E peek(); // 查看队首但不移除
E element(); // 可能抛出异常
}
Deque接口:双端队列,支持两端操作:
java复制Deque<String> deque = new ArrayDeque<>();
deque.offerFirst("First");
deque.offerLast("Last");
String first = deque.pollFirst();
String last = deque.pollLast();
PriorityQueue:优先级队列,基于堆实现:
java复制Queue<Integer> pq = new PriorityQueue<>();
pq.offer(3);
pq.offer(1);
pq.offer(2);
// 出队顺序是1, 2, 3
3. Map接口体系
3.1 Map根接口
Map接口表示键值对映射:
java复制public interface Map<K, V> {
// 基本操作
V put(K key, V value);
V get(Object key);
V remove(Object key);
boolean containsKey(Object key);
// 视图操作
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
// 内部接口Entry
interface Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
}
}
3.2 HashMap深度解析
HashMap是最常用的Map实现:
java复制public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// 核心字段
transient Node<K,V>[] table; // 哈希表数组
int threshold; // 扩容阈值
final float loadFactor; // 负载因子(默认0.75)
// 节点结构:链表或红黑树
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
}
工作原理:
- 计算key的hashCode,通过扰动函数降低碰撞概率
- 根据hash值确定桶位置:(n - 1) & hash
- 如果发生碰撞,采用链表法解决(JDK8后链表长度>8转为红黑树)
- 当size > capacity * loadFactor时,进行扩容(容量翻倍)
使用示例:
java复制Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.get("a"); // 返回1
// 遍历方式1:entrySet
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 遍历方式2:Java8 forEach
map.forEach((k, v) -> System.out.println(k + ": " + v));
3.3 其他Map实现
- LinkedHashMap:保持插入顺序或访问顺序:
java复制Map<String, Integer> lruCache = new LinkedHashMap<>(
16, 0.75f, true // accessOrder设置为true实现LRU
);
- TreeMap:基于红黑树的NavigableMap实现:
java复制TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("b", 2);
treeMap.put("a", 1);
// 自动按键排序:a=1, b=2
- ConcurrentHashMap:线程安全的HashMap:
java复制ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("a", 1);
// 原子操作
concurrentMap.computeIfAbsent("b", k -> 2);
4. 迭代器与工具类
4.1 Iterator模式
java复制public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
// JDK 8+ 新增
default void forEachRemaining(Consumer<? super E> action) {
while (hasNext())
action.accept(next());
}
}
使用示例:
java复制List<String> list = Arrays.asList("a", "b", "c");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
4.2 Collections工具类
java复制// 创建不可变集合
List<String> immutableList = Collections.unmodifiableList(list);
Set<String> singletonSet = Collections.singleton("唯一元素");
// 同步包装
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 算法操作
Collections.sort(list, Comparator.reverseOrder());
Collections.binarySearch(list, "target");
Collections.rotate(list, 2); // 旋转操作
实用方法:
emptyList()/emptySet()/emptyMap():返回空集合singleton()/singletonList()/singletonMap():创建单元素集合nCopies(n, o):创建包含n个相同元素的列表disjoint(c1, c2):判断两个集合是否不相交
5. 高级话题与性能优化
5.1 Fail-Fast与Fail-Safe机制
Fail-Fast:快速失败,检测到并发修改抛出ConcurrentModificationException:
java复制List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if (s.equals("b")) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
Fail-Safe:安全失败,遍历集合的副本:
java复制ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
for (String key : map.keySet()) {
map.remove(key); // 不会抛出异常
}
5.2 集合初始化优化
- 指定初始容量:
java复制// ArrayList默认初始容量10,HashMap默认16
List<String> list = new ArrayList<>(100); // 避免频繁扩容
Map<String, Integer> map = new HashMap<>(32, 0.75f);
- 负载因子选择:
- 负载因子高:减少空间开销,增加哈希碰撞
- 负载因子低:减少哈希碰撞,增加空间开销
- 默认0.75是时间和空间的折中
5.3 并行流处理
Java 8引入的Stream API可以方便地并行处理集合:
java复制List<String> list = Arrays.asList("a", "b", "c");
// 顺序流
list.stream().forEach(System.out::println);
// 并行流
list.parallelStream().forEach(System.out::println);
注意事项:
- 并行流不总是更快,要考虑数据量和操作复杂度
- 确保操作是无状态的,避免共享可变状态
- 注意线程安全问题
6. 实际开发经验分享
6.1 集合选择指南
- 需要键值对存储:
- 需要排序:TreeMap
- 需要插入顺序:LinkedHashMap
- 并发环境:ConcurrentHashMap
- 其他情况:HashMap
- 需要存储单个元素:
- 需要排序:TreeSet
- 需要插入顺序:LinkedHashSet
- 其他情况:HashSet
- 需要列表:ArrayList或LinkedList
6.2 常见陷阱与解决方案
- equals和hashCode不一致:
java复制class Person {
String name;
int age;
// 必须同时重写equals和hashCode
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
- 并发修改异常:
java复制// 错误方式
for (String key : map.keySet()) {
if (key.equals("remove")) {
map.remove(key); // 抛出ConcurrentModificationException
}
}
// 正确方式1:使用迭代器的remove方法
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
if (it.next().equals("remove")) {
it.remove();
}
}
// 正确方式2:Java8+
map.keySet().removeIf(key -> key.equals("remove"));
6.3 性能调优技巧
- 批量操作优于单元素操作:
java复制// 差
for (String s : anotherList) {
list.add(s);
}
// 好
list.addAll(anotherList);
- 预分配容量:
java复制// 知道大概元素数量时
List<String> list = new ArrayList<>(expectedSize);
Map<String, Integer> map = new HashMap<>((int)(expectedSize / 0.75f) + 1);
- 选择合适的遍历方式:
java复制// ArrayList - 随机访问最快
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
}
// LinkedList - 迭代器最快
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
}
// Java5+ 增强for循环
for (String s : list) {
// ...
}
7. Java集合框架的未来发展
随着Java语言的不断演进,集合框架也在持续改进:
- 不可变集合工厂方法(Java 9+):
java复制List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b");
Map<String, Integer> map = Map.of("a", 1, "b", 2);
- 记录类型(Java 16+):
java复制record Point(int x, int y) {}
List<Point> points = new ArrayList<>();
points.add(new Point(1, 2));
- 模式匹配(未来版本):
java复制// 可能的形式
if (list instanceof ArrayList<String> al) {
// 直接使用al
}
在实际项目中,我经常发现开发者没有充分利用集合框架提供的功能,比如Java 8引入的Stream API和Lambda表达式可以大大简化集合操作。例如,以下代码传统写法:
java复制List<String> filtered = new ArrayList<>();
for (String s : list) {
if (s.startsWith("a")) {
filtered.add(s.toUpperCase());
}
}
可以用Stream API简化为:
java复制List<String> filtered = list.stream()
.filter(s -> s.startsWith("a"))
.map(String::toUpperCase)
.collect(Collectors.toList());
这种写法不仅更简洁,而且在并行处理大数据集时性能更好。建议开发者多学习现代Java集合操作的写法,这能显著提高代码质量和开发效率。