Java 从入门到精通(九):集合框架入门,List、Set、Map 到底该怎么选?
学完 String、包装类、抽象类、接口这些内容之后,很多人会发现:
Java 语法自己好像懂了不少,但一到真正写业务代码,最常见的问题却变成了另一种:
- 一组数据到底该用数组,还是集合?
- List、Set、Map 看起来都能"装东西",到底差别在哪?
- 为什么有的地方要去重,有的地方又必须保留顺序?
- ArrayList 和 LinkedList 面试常问,但平时到底怎么选?
- HashMap 为什么这么常用?它和 Hashtable、TreeMap 又是什么关系?
这些问题,表面上是在选容器,实际上是在训练一种很重要的能力:
根据业务需求,选择合适的数据结构。
集合框架,就是 Java 日常开发里最常用的一套"装数据、管数据、查数据"的工具箱。
这一篇不堆 API,也不背定义,直接把最常用的集合思路讲清楚。
一、为什么数组不够,还要有集合?
很多人最早接触"装一批数据",都是从数组开始:
java
int[] nums = new int[5];
nums[0] = 10;
nums[1] = 20;
数组当然很重要,但它有几个明显限制:
- 长度固定,创建后不能动态扩容
- 插入、删除不方便
- 只能处理一类比较基础的存储场景
- 缺少丰富的方法支持
比如你要保存一批用户名字,而且数量不确定:
java
String[] names = new String[100];
如果最后只有 8 个人,就浪费空间;如果来了 101 个人,又不够用。
所以 Java 提供了集合框架,让我们可以:
- 动态存储数据
- 方便地增删改查
- 根据不同场景选不同容器
- 借助统一接口来写更灵活的代码
可以简单理解为:
- 数组:更底层、更固定
- 集合:更灵活、更适合业务开发
二、集合框架整体怎么理解?
Java 集合框架最核心可以分成三大类:
- List:有序、可重复
- Set:无重复
- Map:键值对存储
先看一个最直观的理解方式:
1. List 像"排队名单"
关注的是:
- 元素有顺序
- 可以按下标取值
- 允许重复
例如:课程章节、评论列表、消息记录。
2. Set 像"去重名单"
关注的是:
- 元素不能重复
- 通常不强调按下标访问
- 更适合判重、去重
例如:用户标签、访问过的页面 ID、抽奖去重名单。
3. Map 像"字典/通讯录"
关注的是:
- 每个值都对应一个键
- 通过 key 查 value
- 键不能重复
例如:
- 学号 -> 学生对象
- 用户 id -> 用户信息
- 配置项名称 -> 配置值
所以你先别急着记类名,先记住:
List、Set、Map 不是平级"功能重复"的容器,而是三种完全不同的数据组织方式。
三、List:有序、可重复,最常用的线性集合
List 的特点很简单:
- 插入顺序通常会保留
- 可以通过索引访问
- 允许重复元素
最常用的实现类有:
ArrayListLinkedList
1. ArrayList 最常用
java
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("苹果");
System.out.println(fruits); // [苹果, 香蕉, 苹果]
System.out.println(fruits.get(1)); // 香蕉
}
}
从这个例子可以看出来:
- 顺序保留了
- 重复元素允许存在
- 可以通过
get(index)取指定位置的数据
2. ArrayList 适合什么场景?
大多数业务代码里,如果你只是需要"存一批有顺序的数据",优先就可以想到 ArrayList。
它适合:
- 读取多、随机访问多
- 末尾追加多
- 日常大多数列表类需求
例如:
- 商品列表
- 搜索结果列表
- 文章评论列表
3. LinkedList 是什么?
LinkedList 底层思路是链表。
java
import java.util.LinkedList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<String> list = new LinkedList<>();
list.add("A");
list.add("B");
list.add("C");
System.out.println(list);
}
}
它也实现了 List,所以用法表面上很像。
但在实际开发里:
- 如果你经常按索引取值,
ArrayList通常更合适 - 如果你特别强调头尾插入删除,
LinkedList在某些场景更自然
不过要说一句实话:
现代 Java 业务开发里,ArrayList 的使用频率远高于 LinkedList。
很多初学者被"链表插入快"这句话带偏,结果把 LinkedList 用到了完全没必要的地方。
入门阶段先记住一条经验:
默认先用 ArrayList,除非你非常明确地需要链表特性。
四、Set:不允许重复,核心价值是去重
Set 最大的特点就是:
不允许重复元素。
最常用的是:
HashSetLinkedHashSetTreeSet
1. HashSet 的基本用法
java
import java.util.HashSet;
import java.util.Set;
public class Demo {
public static void main(String[] args) {
Set<String> cities = new HashSet<>();
cities.add("北京");
cities.add("上海");
cities.add("北京");
System.out.println(cities);
}
}
虽然加了两次"北京",但 Set 里只会保留一个。
这就非常适合做:
- 数据去重
- 成员判断
- 标签汇总
例如,你想统计一篇文章中出现过哪些不同的单词,就很适合用 Set。
2. Set 为什么不能用索引?
因为 Set 关注的重点不是"第几个元素",而是"某个元素是否存在"。
它和 List 的设计目标不同:
- List 更像线性表
- Set 更像数学集合
所以你不应该一边用 Set,一边还想着 get(0)、get(1) 这种操作。
如果你特别关心顺序,应该先想清楚自己真正要的是不是 List。
3. HashSet、LinkedHashSet、TreeSet 怎么区分?
可以先用一句话记:
HashSet:去重,通常不保证遍历顺序LinkedHashSet:去重,同时保留插入顺序TreeSet:去重,并且自动排序
例如:
java
import java.util.Set;
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
Set<Integer> scores = new TreeSet<>();
scores.add(90);
scores.add(75);
scores.add(88);
scores.add(90);
System.out.println(scores); // [75, 88, 90]
}
}
所以如果需求是:
- 只去重:先想
HashSet - 去重且保留加入顺序:想
LinkedHashSet - 去重且排序:想
TreeSet
五、Map:真正高频的键值对容器
如果说 List 是"最常见的线性容器",那 Map 就是"最常见的查找容器"。
它保存的是:
key -> value
最常用实现类:
HashMapLinkedHashMapTreeMap
1. HashMap 基本用法
java
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) {
Map<Integer, String> students = new HashMap<>();
students.put(1001, "张三");
students.put(1002, "李四");
students.put(1003, "王五");
System.out.println(students.get(1002)); // 李四
}
}
这里就不是"存一排数据",而是"通过 key 找 value"。
所以 Map 非常适合:
- id 到对象的映射
- 配置项存储
- 统计计数
- 缓存结构
2. Map 和 List 最本质的区别
很多人初学时容易混:
- List 是通过位置找元素
- Map 是通过键找元素
比如:
java
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
System.out.println(list.get(1));
这里你是按位置取第二个人。
而 Map 是:
java
Map<Integer, String> map = new HashMap<>();
map.put(1001, "张三");
map.put(1002, "李四");
System.out.println(map.get(1002));
这里你是按学号取人。
所以当你的需求里天然存在"编号、名称、唯一标识"这类键时,Map 通常更合适。
3. 键不能重复是什么意思?
java
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 5);
map.put("apple", 10);
System.out.println(map); // apple 对应的值会被覆盖
Map 中 key 不能重复。
如果重复 put 同一个 key,旧值会被新值覆盖。
这其实很合理:
因为一个 key 代表的是同一个"索引入口"。
4. 一个非常常见的计数场景
java
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) {
String[] words = {"java", "spring", "java", "mysql", "java", "spring"};
Map<String, Integer> countMap = new HashMap<>();
for (String word : words) {
if (countMap.containsKey(word)) {
countMap.put(word, countMap.get(word) + 1);
} else {
countMap.put(word, 1);
}
}
System.out.println(countMap);
}
}
这个例子特别典型。
以后你一看到:
- 统计词频
- 统计用户访问次数
- 统计商品销量次数
脑子里就要想到:
这大概率是 Map 的场景。
六、到底该选 List、Set 还是 Map?
这是整篇最关键的问题。
可以按下面这套思路判断:
1. 你要的是"一列数据",还是"键值映射"?
- 如果是"一批元素排起来",优先想
List - 如果是"键对应值",优先想
Map
2. 这一批元素能不能重复?
- 能重复:
List - 不能重复:
Set
3. 你关不关心顺序?
- 关心插入顺序:
ArrayList/LinkedHashSet/LinkedHashMap - 关心排序结果:
TreeSet/TreeMap - 不关心顺序,只关心查找和存储:
HashSet/HashMap
4. 你是不是要按下标访问?
- 是:通常应该用
List - 不是,而是判断元素是否存在:更像
Set - 不是,而是通过 key 查 value:更像
Map
七、开发里最常见的选择口诀
如果你现在还没有完全形成感觉,可以先背一个非常实用的版本:
- 默认列表数据:
ArrayList - 默认去重集合:
HashSet - 默认键值映射:
HashMap
这三个,是 Java 集合里最常用的"默认起手式"。
很多真实项目里,你写的大部分集合代码,绕不开它们。
等你后面遇到更明确的需求,再升级成:
- 要保序:
LinkedHashMap/LinkedHashSet - 要排序:
TreeMap/TreeSet - 要线程安全:并发集合(后面会讲)
八、几个初学者最容易踩的坑
1. 以为集合什么都能装,结果类型混乱
不要这样写:
java
List list = new ArrayList();
list.add("hello");
list.add(123);
list.add(true);
这会让代码非常难维护。
更好的写法是明确类型:
java
List<String> list = new ArrayList<>();
这里的泛型我们下一篇会详细讲,但你现在先记住:
集合尽量带类型。
2. 一边遍历一边乱删元素
有些人会在遍历集合时直接删除元素,结果容易出问题。
这类问题后面讲迭代器和并发修改时会系统展开。
你现在先建立一个意识:
集合在遍历、修改时要格外小心。
3. 把 Set 当成有顺序的列表来用
如果你的业务明确依赖"第一个、第二个、第三个",那大概率不该用普通 HashSet。
4. 把 Map 理解成"高级 List"
Map 不是按位置访问的容器,它的核心是通过 key 建立索引。
只要你想清楚这一点,很多场景就不会选错。
九、一个简单的业务例子,把三者连起来
假设你在做一个课程系统:
- 用
List保存课程章节,因为章节有顺序 - 用
Set保存学生标签,因为标签要去重 - 用
Map保存学号和学生对象的映射,因为要按学号快速找到人
示意代码:
java
import java.util.*;
public class Demo {
public static void main(String[] args) {
List<String> chapters = new ArrayList<>();
chapters.add("第一章:Java 入门");
chapters.add("第二章:流程控制");
chapters.add("第三章:集合框架");
Set<String> tags = new HashSet<>();
tags.add("认真");
tags.add("活跃");
tags.add("认真");
Map<Integer, String> studentMap = new HashMap<>();
studentMap.put(1001, "张三");
studentMap.put(1002, "李四");
System.out.println(chapters);
System.out.println(tags);
System.out.println(studentMap.get(1001));
}
}
只要你能看懂这个例子,说明你对集合框架最核心的第一层已经建立起来了。
十、这一篇你真正该记住什么?
不要试图一次把所有集合类全背下来。
入门阶段,先把下面这几条吃透:
- List :有序、可重复,典型实现是
ArrayList - Set :无重复,典型实现是
HashSet - Map :键值对存储,典型实现是
HashMap - 顺序、去重、映射,是三种不同需求,不要混着选
- 大多数业务开发里,先会正确选容器,比背底层源码更重要
十一、结语
集合框架是 Java 真正进入"写业务代码"阶段的分水岭。
前面学类、对象、字符串,更多是在理解语言本身。
而从集合开始,你会越来越频繁地思考:
- 数据怎么组织?
- 查找效率怎么保证?
- 哪种结构更贴近业务需求?
这时候,Java 就不只是"会写语法",而开始进入"会设计数据"的阶段了。
先把 List、Set、Map 三者分清楚,后面再学泛型、迭代器、并发集合、Stream,都会顺很多。
下一篇,我们继续:Java 从入门到精通(十):泛型与集合实战,为什么 Java 一定要有泛型?