Java 容器梳理
Java 的容器框架是语言的核心组成部分,为开发者提供了存储、操作和检索数据的强大工具。本文梳理Java 容器的基础知识、框架结构、核心机制及实用注意事项。
数组与容器的对比
在 Java 中,存储数据的主要方式是数组和容器。二者功能相似,但在以下方面存在显著差异:
-
大小灵活性:
- 数组的长度固定,创建时确定。
- 容器的长度可变,可根据需要动态调整。
-
数据类型:
- 数组可以存储基本数据类型(如
int
、double
)和引用数据类型(如对象)。 - 容器只能存储引用数据类型,基本数据类型需转换为对应的包装类(如
Integer
)才能存入。
- 数组可以存储基本数据类型(如
Java 容器框架
Java 容器框架主要分为两大接口:Collection
和 Map
。以下是其结构概览:
Collection
Collection
表示一组独立的元素序列,受特定规则约束。它分为三个子接口:
-
List
:- 按插入顺序保存元素。
- 常见实现:
ArrayList
:基于动态Object[]
数组,适合快速随机访问。LinkedList
:双向链表(JDK 1.7 后取消循环),适合频繁插入/删除。Vector
:通过synchronized
修饰的线程安全Object[]
数组。
-
Set
:- 禁止重复元素。
- 常见实现:
HashSet
:无序,基于HashMap
实现。LinkedHashSet
:保持插入顺序,基于LinkedHashMap
。TreeSet
:按自然顺序或自定义比较器排序,基于红黑树。
-
Queue
:- 按队列规则组织元素(如 FIFO 或优先级)。
- 常见实现:
PriorityQueue
:基于Object[]
数组的小顶堆。DelayQueue
:延迟队列。ArrayDeque
:动态数组实现的双端队列。LinkedList
:链表形式的双端队列。
Map
Map
管理键值对,键唯一,可通过键查找值。常见实现包括:
HashMap
:- JDK 1.8 前:数组 + 链表(通过拉链法解决哈希冲突)。
- JDK 1.8 后:链表长度超 8(且数组长度 ≥ 64)时转为红黑树,优化查找效率。
LinkedHashMap
:继承HashMap
,增加双向链表保持插入顺序。Hashtable
:线程安全的数组 + 链表结构。TreeMap
:基于红黑树,保证键的有序性。
有关框架结构的详细图示,可参考原文档。
容器的核心机制
Java 容器的共性和特性依赖于以下关键机制,理解这些机制有助于掌握其原理和应用。
泛型
Java 1.5 引入泛型,确保容器的类型安全。未使用泛型时,容器可能混杂类型,隐患巨大:
List<Object> list = new ArrayList<>();
list.add("123");
list.add(123); // 字符串和整数混存
使用泛型后,类型检查在编译期完成:
List<String> list = new ArrayList<>();
list.add("123");
// list.add(123); // 编译错误
Iterable 和 Iterator
Iterable
和 Iterator
接口用于遍历容器元素。Collection
继承 Iterable
,所有集合都支持迭代。
Iterator
提供hasNext()
、next()
和remove()
等方法。- 迭代器模式:标准化遍历,隐藏容器内部实现。
示例:
List<Integer> list = Arrays.asList(1, 2, 3);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Fail-Fast 机制:迭代时修改容器结构(如增删元素)可能抛出 ConcurrentModificationException
。《阿里巴巴 Java 开发手册》建议避免在 for-each
中直接调用集合的 remove/add
,应使用 Iterator
的方法。Java 8+ 还提供 removeIf()
:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
list.removeIf(n -> n % 2 == 0); // 删除偶数
System.out.println(list); // [1, 3]
Comparable 和 Comparator
这两个接口用于元素比较和排序:
Comparable
:类内部实现compareTo()
,排序逻辑与对象绑定。Comparator
:外部定义compare()
,灵活性更高。
Comparable
示例:
class User implements Comparable<User> {
private int age;
private String name;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(User o) {
return this.age - o.age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
List<User> users = new ArrayList<>(Arrays.asList(new User("A", 18), new User("B", 17)));
Collections.sort(users); // 按年龄排序
Comparator
示例:
List<User> users = new ArrayList<>(Arrays.asList(new User("A", 18), new User("B", 17)));
Collections.sort(users, Comparator.comparingInt(user -> user.age));
Cloneable
实现**Cloneable
** 接口并重写 Object.clone()
可启用对象克隆,否则调用 clone()
会抛出 CloneNotSupportedException
。默认 clone()
为浅拷贝,深拷贝需自定义实现。
Fail-Fast 机制
Fail-Fast 是 Java 容器的错误检测机制,迭代时若结构被修改(如增删元素),可能抛出 ConcurrentModificationException
。示例:
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) list.add(i);
new Thread(() -> {
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
try { Thread.sleep(100); } catch (Exception e) {}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) if (i % 2 == 0) list.remove(i);
}).start();
解决方法:
- 使用同步(如
synchronized
或Collections.synchronizedList
),但可能影响性能。 - 使用并发容器,如
CopyOnWriteArrayList
。
容器与线程安全
为在并发环境中安全使用容器,Java 提供:
- 同步容器:如
Vector
、Hashtable
或Collections.synchronizedXXX
包装。 - 并发容器:位于
java.util.concurrent
,如ConcurrentHashMap
和CopyOnWriteArrayList
。
总结
Java 容器通过泛型、迭代器、比较器和错误检测机制(如 fail-fast),提供了高效、类型安全和线程安全的解决方案。从有序的 List
、无重复的 Set
到键值对的 Map
,理解这些机制能让你编写更健壮的代码。