集合框架概述
本教程共 100 篇 · 第 72 篇 · 更新于 2026-08-05 · 约 5 分钟阅读
72. 集合框架概述
本节目标:认清集合框架解决什么问题,记住 Collection 和 Map 两大家族的关系,学完能在一堆实现类里选对开头用的那几个。
数组不够用了
前面学数组时你会发现,数组有个硬伤:长度固定,装多少在写代码时就得定死。
String[] names = new String[3]; // 最多装 3 个,第 4 个就爆
```bash
实际程序里,数据往往是变长的。用户今天注册 3 个,明天来 300 个,你不可能每次都改代码重编译。更麻烦的是,数组没有提供「删掉中间一个」「查某个元素在不在」这类现成方法,都得自己写循环实现。
集合框架就是为这事生的:它提供一组「能长大、能缩小、能随便增删」的容器,把增删查改的细节都封装好,你只管往里扔对象、调用现成方法。
## 一个框架,两种容器
Java 的集合框架(Collections Framework,简称 JCF)把容器分成两大家族。
```text
Collection(单列,装一个个元素)
├── List 有序、可重复
├── Set 无序、不可重复
└── Queue 队列,先进先出
Map(双列,装「键=值」配对)
└── HashMap / TreeMap / LinkedHashMap
一句话区分:Collection 像一串糖葫芦,每个位置插一个果子;Map 像一张通讯录,靠「名字」查「号码」。这是整套框架最底层的划分,后面所有接口都挂在这两个分支下面。
接口和实现分开
集合框架的精髓是「接口管约定,实现管细节」。你写代码时尽量面向接口,别死绑某个类。
// 面向 List 接口,底层换成 LinkedList 也不影响调用方
List<String> list = new ArrayList<>();
list.add("苹果");
list.add("香蕉");
```bash
这样做的好处是灵活:哪天发现 ArrayList 不够快,只要把 `new ArrayList<>()` 换成 `new LinkedList<>()`,下面所有 `add`、`get` 调用都不用改。统一的接口让「换实现」变成一行代码的事。
常用的「接口—实现」配对长这样:
- `List` → `ArrayList`(最常用)、`LinkedList`
- `Set` → `HashSet`(最常用)、`LinkedHashSet`、`TreeSet`
- `Queue`/`Deque` → `ArrayDeque`、`PriorityQueue`
- `Map` → `HashMap`(最常用)、`LinkedHashMap`、`TreeMap`
> [!NOTE]
> 记住一条经验:日常 90% 场景用 `ArrayList` + `HashSet` + `HashMap` 就够,先把这三个吃透,其他的用到再查。
## 为什么要用泛型
早期的集合能装任何 `Object`,取出来再强转,转错了运行才崩。
```java
// 历史兼容写法(别在主线上用)
List list = new ArrayList(); // 不写类型,装什么都行
list.add("字符串");
list.add(123);
String s = (String) list.get(1); // 运行时 ClassCastException
Java 5 引入泛型后,类型在编译期就锁死,写错直接红字报错,安全又省心。
List<String> list = new ArrayList<>(); // 只装 String
list.add("字符串");
// list.add(123); // 编译期就拦住,根本跑不到运行
```bash
本书主线统一用泛型写法(Java 25 LTS 默认如此)。所谓「裸类型」`List` 只在讲历史兼容时露一面。泛型还要求:放进集合的元素最好是「不可变」的,尤其是当元素要当 Set 成员或 Map 的键时,运行中改了它的内容,集合内部就乱套了。
## 该用哪个容器
选容器先问自己三个问题:
1. 要的是「一串元素」还是「键值配对」?前者用 Collection,后者用 Map。
2. 元素能不能重复?能重复用 List,不能重复用 Set。
3. 要不要保序、要不要排序?保插入顺序用 LinkedHashSet/LinkedHashMap,要自动排序用 TreeSet/TreeMap。
> [!TIP]
> 拿不准就先用 `ArrayList` 和 `HashMap`。它们性能均衡、资料最多,等真遇到瓶颈再换更专门的实现。
## 历史兼容的类,先认识别用
你会在老代码里看到 `Vector`、`Hashtable`、`Stack` 这几个名字。它们是 Java 1.0 的「古董」,方法上都加了同步锁,性能差、设计也过时。
```java
// 历史兼容写法,新代码不要这么写
Vector<String> v = new Vector<>(); // 方法全同步,慢
Hashtable<String, Integer> h = new Hashtable<>(); // 同样是老古董
Warning主线代码不要碰
Vector、Hashtable、Stack。需要线程安全用ConcurrentHashMap;需要栈用ArrayDeque(见第 74、79 章)。它们只作为「历史兼容」对照出现,本教程不推荐。
跑一个最小的例子
下面这段能直接编译运行,感受下集合的顺手:
import java.util.*;
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>(List.of("甲", "乙", "甲"));
Set<String> set = new HashSet<>(list); // 自动去重
System.out.println("列表:" + list); // [甲, 乙, 甲]
System.out.println("集合:" + set); // [甲, 乙]
}
}
```bash
注意 `List.of(...)` 是 Java 9 起的工厂方法,比 `new ArrayList<>()` 再一个个 add 更简洁;它返回的是不可变列表,适合当初始数据。
## 集合里装的是对象,不是基本类型
有个常见疑问:集合能装 `int`、`double` 吗?答案是集合的泛型参数只能用引用类型,所以你写 `List<int>` 会直接报错。
```java
List<Integer> nums = new ArrayList<>();
nums.add(10); // 这里发生了自动装箱:10 被包成 Integer
nums.add(20);
int x = nums.get(0); // 自动拆箱:Integer 变回 int
Java 5 的「自动装箱/拆箱」让 int 和 Integer 之间自动转换,写起来像直接装了 int。但要心里有数:集合里存的始终是对象。大量装小数字时,每个都会生成一个 Integer 对象,极端性能场景要注意这点。
Tip记不住哪些是引用类型时,回想基本类型只有八个:
byte short int long float double char boolean。其余都是对象,都能直接当集合的泛型参数。
不可变集合也值得认识
除了可变的 ArrayList 这类,Java 9 起还提供了一组「不可变集合」:List.of、Set.of、Map.of。它们创建后不能增删改,适合当固定配置或返回值,既安全又省内存。
List<String> fixed = List.of("红", "黄", "蓝");
// fixed.add("绿"); // 抛 UnsupportedOperationException
```bash
可变集合负责日常增删,不可变集合负责「定下来就不再变」的数据,两者配合着用。
## 小结
集合框架用一套统一接口,把「装数据」这件麻烦事标准化了。Collection 管单列元素,Map 管键值配对;每个接口下有好几个实现类,按「要不要重复、要不要保序、要不要排序」来挑。集合只装对象、靠自动装箱兼容基本类型,还有可变与不可变两套选择。
后面几章,我们一个接口一个接口拆开讲,从最常用、最该先学的 `List` 和 `ArrayList` 开始。