Java 集合框架(Collection Framework)详解
Java 集合框架是用于存储和操作一组对象的统一架构,位于 java.util
包下。它解决了数组的固定长度、类型单一等局限性,提供了灵活、高效的数据结构和操作方法。本文将从核心接口到常用实现类,结合代码示例详细讲解。
一、集合框架整体结构
集合框架的核心是 Collection
和 Map
两大接口,它们分别代表两种不同的数据结构:
Collection
:存储单一元素的集合(如列表、集合),元素是独立的个体。Map
:存储键值对(Key-Value) 的集合(如字典),元素以key:value
形式存在,key
唯一,value
可重复。
框架结构图(简化版):
java.util
├─ Collection(接口) // 单元素集合根接口
│ ├─ List(接口) // 有序、可重复
│ │ ├─ ArrayList(实现类) // 动态数组
│ │ └─ LinkedList(实现类) // 双向链表
│ └─ Set(接口) // 无序、不可重复
│ ├─ HashSet(实现类) // 哈希表
│ └─ TreeSet(实现类) // 红黑树(有序)
│
└─ Map(接口) // 键值对集合根接口
├─ HashMap(实现类) // 哈希表(key无序)
├─ TreeMap(实现类) // 红黑树(key有序)
└─ Hashtable(实现类) // 哈希表(线程安全,过时)
二、核心接口:Collection
Collection
是所有单元素集合的根接口,定义了操作集合的通用方法(如添加、删除、遍历等)。其常用方法如下:
方法声明 | 功能描述 |
---|---|
boolean add(E e) | 添加元素到集合(成功返回true ) |
boolean remove(Object o) | 从集合中删除指定元素 |
void clear() | 清空集合所有元素 |
boolean contains(Object o) | 判断集合是否包含指定元素 |
int size() | 返回集合元素个数 |
boolean isEmpty() | 判断集合是否为空 |
Iterator<E> iterator() | 返回迭代器(用于遍历集合) |
1. List
接口(有序、可重复)
List
继承自 Collection
,特点是:
- 有序:元素按插入顺序排列,可通过索引(
0
开始)访问。 - 可重复:允许存储相同的元素(
equals()
判定为true
)。
常用方法(除 Collection
通用方法外):
方法声明 | 功能描述 |
---|---|
E get(int index) | 获取指定索引的元素 |
E set(int index, E element) | 修改指定索引的元素(返回旧元素) |
void add(int index, E element) | 在指定索引插入元素 |
E remove(int index) | 删除指定索引的元素(返回被删除元素) |
int indexOf(Object o) | 返回元素首次出现的索引(未找到返回 -1 ) |
(1)ArrayList
实现类
- 底层结构:动态数组(容量可自动扩容,默认初始容量
10
,满后扩容为原来的1.5
倍)。 - 特点:
- 查询快(通过索引直接访问,时间复杂度
O(1)
)。 - 增删慢(需移动元素,尤其中间位置,时间复杂度
O(n)
)。
- 查询快(通过索引直接访问,时间复杂度
- 适用场景:频繁查询、少量增删的场景(如数据展示列表)。
代码示例:ArrayList
基本操作
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
// 创建ArrayList(泛型指定元素类型为String)
List<String> list = new ArrayList<>();
// 1. 添加元素
list.add("Java"); // 末尾添加
list.add("Python");
list.add(1, "C++"); // 指定索引插入(原索引1及之后元素后移)
System.out.println("添加后:" + list); // [Java, C++, Python]
// 2. 访问元素(通过索引)
String element = list.get(0);
System.out.println("索引0的元素:" + element); // Java
// 3. 修改元素
String old = list.set(2, "JavaScript"); // 修改索引2的元素
System.out.println("修改后:" + list); // [Java, C++, JavaScript]
System.out.println("被替换的元素:" + old); // Python
// 4. 删除元素
String removed = list.remove(1); // 删除索引1的元素
System.out.println("删除后:" + list); // [Java, JavaScript]
System.out.println("被删除的元素:" + removed); // C++
// 5. 遍历元素(3种方式)
System.out.println("遍历方式1:普通for循环");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("遍历方式2:增强for循环(foreach)");
for (String s : list) {
System.out.println(s);
}
System.out.println("遍历方式3:迭代器(Iterator)");
java.util.Iterator<String> it = list.iterator();
while (it.hasNext()) { // 判断是否有下一个元素
String s = it.next(); // 获取下一个元素
System.out.println(s);
}
}
}
(2)LinkedList
实现类
- 底层结构:双向链表(每个节点包含
prev
(前节点)、next
(后节点)指针)。 - 特点:
- 增删快(只需修改指针,时间复杂度
O(1)
,尤其首尾位置)。 - 查询慢(需从头/尾遍历,时间复杂度
O(n)
)。
- 增删快(只需修改指针,时间复杂度
- 适用场景:频繁增删、少量查询的场景(如队列、栈)。
代码示例:LinkedList
特有操作
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
// 添加元素(支持首尾快速操作)
linkedList.add("A"); // 末尾添加
linkedList.addFirst("B"); // 头部添加
linkedList.addLast("C"); // 末尾添加
System.out.println("链表内容:" + linkedList); // [B, A, C]
// 特有方法:操作首尾元素
System.out.println("首元素:" + linkedList.getFirst()); // B
System.out.println("尾元素:" + linkedList.getLast()); // C
// 移除首尾元素
linkedList.removeFirst();
linkedList.removeLast();
System.out.println("移除首尾后:" + linkedList); // [A]
}
}
2. Set
接口(无序、不可重复)
Set
继承自 Collection
,特点是:
- 无序:元素插入顺序不保证(
HashSet
),或按特定规则排序(TreeSet
)。 - 不可重复:不允许存储相同的元素(
equals()
判定为true
则视为重复)。
常用方法:与 Collection
一致(无索引相关方法,因为无序)。
(1)HashSet
实现类
- 底层结构:哈希表(数组 + 链表/红黑树),依赖元素的
hashCode()
和equals()
方法保证唯一性。 - 去重原理:
- 计算元素的
hashCode()
得到哈希值,确定在哈希表中的存储位置。 - 若该位置无元素,直接存入;若有元素,通过
equals()
比较,相同则不存,不同则以链表/红黑树形式存储(哈希冲突处理)。
- 计算元素的
- 特点:
- 无序(存储顺序 ≠ 插入顺序)。
- 查找、添加、删除效率高(平均时间复杂度
O(1)
)。
- 注意:存储自定义对象时,必须重写
hashCode()
和equals()
方法,否则无法正确去重。
代码示例:HashSet
去重与自定义对象
import java.util.HashSet;
import java.util.Set;
// 自定义对象:学生(需重写hashCode和equals)
class Student {
private String id;
private String name;
public Student(String id, String name) {
this.id = id;
this.name = name;
}
// 重写equals:id相同则视为同一学生
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id.equals(student.id);
}
// 重写hashCode:与equals保持一致(id相同则hashCode相同)
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public String toString() {
return "Student{id='" + id + "', name='" + name + "'}";
}
}
public class HashSetDemo {
public static void main(String[] args) {
Set<String> strSet = new HashSet<>();
// 添加重复元素(自动去重)
strSet.add("苹果");
strSet.add("香蕉");
strSet.add("苹果"); // 重复,不添加
System.out.println("字符串集合:" + strSet); // [香蕉, 苹果](无序)
// 存储自定义对象(需重写hashCode和equals)
Set<Student> studentSet = new HashSet<>();
studentSet.add(new Student("001", "张三"));
studentSet.add(new Student("002", "李四"));
studentSet.add(new Student("001", "张三三")); // id重复,视为同一对象,不添加
System.out.println("学生集合:" + studentSet);
// 输出:[Student{id='002', name='李四'}, Student{id='001', name='张三'}]
}
}
(2)TreeSet
实现类
- 底层结构:红黑树(自平衡二叉查找树),元素会按自然顺序或自定义比较器排序。
- 特点:
- 有序(按排序规则排列,而非插入顺序)。
- 查找、添加、删除时间复杂度
O(log n)
。
- 排序方式:
- 自然排序:元素实现
Comparable
接口,重写compareTo()
方法(如Integer
、String
已实现)。 - 自定义排序:创建
TreeSet
时传入Comparator
比较器。
- 自然排序:元素实现
代码示例:TreeSet
排序
import java.util.Comparator;
import java.util.TreeSet;
// 自定义对象:商品(实现Comparable接口,按价格自然排序)
class Product implements Comparable<Product> {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// 自然排序:按价格升序(小→大)
@Override
public int compareTo(Product o) {
// 价格相等时,按名称排序(避免价格相同的元素被去重)
if (this.price == o.price) {
return this.name.compareTo(o.name);
}
return Double.compare(this.price, o.price); // 等价于 (int)(this.price - o.price)
}
@Override
public String toString() {
return name + "(" + price + "元)";
}
}
public class TreeSetDemo {
public static void main(String[] args) {
// 1. 自然排序(元素实现Comparable)
TreeSet<Product> products = new TreeSet<>();
products.add(new Product("手机", 3999));
products.add(new Product("电脑", 5999));
products.add(new Product("耳机", 999));
System.out.println("按价格升序:" + products);
// 输出:[耳机(999.0元), 手机(3999.0元), 电脑(5999.0元)]
// 2. 自定义排序(传入Comparator,按价格降序)
TreeSet<Product> descProducts = new TreeSet<>(
new Comparator<Product>() {
@Override
public int compare(Product p1, Product p2) {
return Double.compare(p2.price, p1.price); // 反转比较顺序
}
}
);
descProducts.addAll(products);
System.out.println("按价格降序:" + descProducts);
// 输出:[电脑(5999.0元), 手机(3999.0元), 耳机(999.0元)]
}
}
三、核心接口:Map
Map
是键值对集合的根接口,特点是:
- 元素以
key:value
形式存在,key
唯一(重复添加会覆盖旧值),value
可重复。 key
和value
均可为null
(HashMap
允许key
为null
一次,Hashtable
不允许)。
常用方法:
方法声明 | 功能描述 |
---|---|
V put(K key, V value) | 添加键值对(返回被覆盖的旧值,无则返回null ) |
V get(Object key) | 根据 key 获取 value (无则返回null ) |
V remove(Object key) | 根据 key 删除键值对(返回被删除的value ) |
boolean containsKey(Object key) | 判断是否包含指定 key |
boolean containsValue(Object value) | 判断是否包含指定 value |
Set<K> keySet() | 返回所有 key 的 Set 集合 |
Collection<V> values() | 返回所有 value 的 Collection 集合 |
Set<Map.Entry<K,V>> entrySet() | 返回所有键值对(Entry 对象)的 Set 集合 |
int size() | 返回键值对数量 |
void clear() | 清空所有键值对 |
1. HashMap
实现类
- 底层结构:哈希表(数组 + 链表/红黑树),
key
的存储依赖hashCode()
和equals()
(同HashSet
)。 - 特点:
key
无序(存储顺序 ≠ 插入顺序)。- 查找、添加、删除效率高(平均
O(1)
)。 - 非线程安全(多线程操作可能出问题)。
- 适用场景:一般键值对存储,如缓存、配置映射等。
代码示例:HashMap
基本操作
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapDemo {
public static void main(String[] args) {
// 创建HashMap(key为String,value为Integer)
Map<String, Integer> map = new HashMap<>();
// 1. 添加键值对
map.put("Java", 90);
map.put("Python", 85);
map.put("Java", 95); // key重复,覆盖旧值(90→95)
System.out.println("map内容:" + map); // {Python=85, Java=95}(无序)
// 2. 获取value
int javaScore = map.get("Java");
System.out.println("Java分数:" + javaScore); // 95
System.out.println("C++分数(不存在):" + map.get("C++")); // null
// 3. 遍历方式(3种)
System.out.println("方式1:遍历key,再获取value");
Set<String> keys = map.keySet(); // 获取所有key
for (String key : keys) {
System.out.println(key + " → " + map.get(key));
}
System.out.println("方式2:直接遍历value(无法获取key)");
for (Integer value : map.values()) {
System.out.println("分数:" + value);
}
System.out.println("方式3:遍历键值对(Entry对象)");
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
// 4. 删除键值对
map.remove("Python");
System.out.println("删除Python后:" + map); // {Java=95}
}
}
2. TreeMap
实现类
- 底层结构:红黑树,
key
会按自然顺序或自定义比较器排序。 - 特点:
key
有序(按排序规则排列)。- 查找、添加、删除时间复杂度
O(log n)
。 - 非线程安全。
- 排序方式:同
TreeSet
(key
实现Comparable
或传入Comparator
)。
代码示例:TreeMap
排序
import java.util.Comparator;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
// 1. 自然排序(key为Integer,默认按数字升序)
TreeMap<Integer, String> numMap = new TreeMap<>();
numMap.put(3, "三");
numMap.put(1, "一");
numMap.put(2, "二");
System.out.println("数字排序:" + numMap); // {1=一, 2=二, 3=三}
// 2. 自定义排序(key为String,按长度降序)
TreeMap<String, Integer> strMap = new TreeMap<>(
new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
// 按字符串长度降序,长度相同则按自然顺序
if (s1.length() != s2.length()) {
return Integer.compare(s2.length(), s1.length());
}
return s1.compareTo(s2);
}
}
);
strMap.put("apple", 5);
strMap.put("banana", 6);
strMap.put("cat", 3);
System.out.println("按字符串长度降序:" + strMap);
// 输出:{banana=6, apple=5, cat=3}(banana长度6最长,cat长度3最短)
}
}
3. Hashtable
实现类(过时)
- 底层结构:哈希表,与
HashMap
类似,但线程安全(方法加了synchronized
锁)。 - 缺点:
- 线程安全导致效率低,不适合高并发场景(推荐用
ConcurrentHashMap
替代)。 - 不允许
key
或value
为null
。
- 线程安全导致效率低,不适合高并发场景(推荐用
- 现状:JDK 1.0 推出,已被
HashMap
取代,仅兼容旧代码时使用。
四、集合框架的关键概念
1. 迭代器(Iterator
)
Iterator
是遍历集合的通用接口,适用于 Collection
体系(List
、Set
),提供统一的遍历方式:
boolean hasNext()
:判断是否有下一个元素。E next()
:获取下一个元素(注意:必须先调用hasNext()
,否则可能抛NoSuchElementException
)。void remove()
:删除当前遍历的元素(需在next()
之后调用)。
示例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("B")) {
it.remove(); // 删除当前元素(B)
}
}
System.out.println(list); // [A, C]
}
}
2. 泛型(Generic)
泛型用于限制集合中元素的类型,避免运行时类型转换错误(编译时检查):
- 声明:
List<String> list = new ArrayList<>();
(String
为泛型类型,限制只能存字符串)。 - 好处:无需手动类型转换,编译时检查类型安全。
示例:
// 无泛型:需强制转换,可能抛ClassCastException
List list = new ArrayList();
list.add("Java");
String s = (String) list.get(0); // 必须转换
// 有泛型:自动类型匹配,编译时检查
List<String> list2 = new ArrayList<>();
list2.add("Java");
String s2 = list2.get(0); // 无需转换
// list2.add(123); // 编译报错(不能添加整数)
3. 线程安全性
- 非线程安全:
ArrayList
、LinkedList
、HashMap
、TreeMap
、HashSet
、TreeSet
(多线程并发修改可能导致异常,如ConcurrentModificationException
)。 - 线程安全:
Vector
(ArrayList
的线程安全版,过时)、Hashtable
(过时)、Collections.synchronizedList()
等工具类包装的集合,推荐使用java.util.concurrent
包下的并发集合(如ConcurrentHashMap
、CopyOnWriteArrayList
)。
五、常用集合对比与选择
集合类型 | 底层结构 | 有序性 | 重复性 | 线程安全 | 效率(增删查) | 适用场景 |
---|---|---|---|---|---|---|
ArrayList | 动态数组 | 插入顺序 | 允许 | 否 | 查快增删慢 | 频繁查询、少量修改 |
LinkedList | 双向链表 | 插入顺序 | 允许 | 否 | 增删快查慢 | 频繁增删(如队列、栈) |
HashSet | 哈希表 | 无序 | 不允许 | 否 | 增删查快 | 去重、无需排序 |
TreeSet | 红黑树 | 排序后 | 不允许 | 否 | 增删查较快 | 去重且需要排序 |
HashMap | 哈希表 | key无序 | key不允许 | 否 | 增删查快 | 一般键值对存储 |
TreeMap | 红黑树 | key排序 | key不允许 | 否 | 增删查较快 | 键值对需要按key排序 |
Hashtable | 哈希表 | key无序 | key不允许 | 是(低效) | 增删查慢 | 兼容旧代码(不推荐新用) |
总结
Java 集合框架通过 Collection
(单元素)和 Map
(键值对)两大体系,提供了丰富的数据结构:
List
(ArrayList
、LinkedList
)适合存储有序可重复的元素,重点关注查询或增删效率。Set
(HashSet
、TreeSet
)适合存储无序不可重复的元素,重点关注去重或排序需求。Map
(HashMap
、TreeMap
)适合存储键值对,重点关注key
的唯一性和排序需求。
实际开发中,需根据有序性、重复性、操作效率、线程安全等需求选择合适的集合类,同时善用泛型和迭代器提高代码安全性和可读性。
如需巩固本文内容,可点击以下链接完成相关练习:点击此处进入练习题。