Java集合实战:List、Map、Set核心用法+避坑指南
一、Java集合基础认知(面试预热)
1.1 为什么Java集合是开发必备?
解决数组固定长度的痛点,支持动态扩容,无需手动管理内存;
提供丰富的操作方法(增删改查、排序、去重),简化开发流程;
适配不同业务场景:有序存储、去重存储、键值对存储等,按需选型;
底层封装了数据结构(链表、数组、哈希表等),无需手动实现复杂逻辑。
1.2 本文核心:三大核心集合体系
Java集合主要分为三大体系,覆盖绝大多数开发场景,核心区别如下:
List(列表):有序、可重复,适合需要按插入顺序存储、允许重复元素的场景;
Set(集合):无序、不重复,适合需要去重、判断元素是否存在的场景;
Map(映射):键唯一、值可重复,适合键值对存储、快速查询的场景。
重点提醒:集合与数组的核心区别——数组长度固定,集合动态扩容;数组可存储基本类型和引用类型,集合仅能存储引用类型(基本类型需用包装类)。
二、List 列表:有序可重复,开发最常用
2.1 核心特性与底层结构
List 是有序集合,元素可重复,支持根据索引快速访问,核心实现类有两个:ArrayList、LinkedList,底层结构差异决定了其性能特点:
ArrayList:底层基于动态数组实现,查询效率高(通过索引访问,O(1)),增删效率低(需移动数组元素,O(n));
LinkedList:底层基于双向链表实现,增删效率高(仅需修改指针,O(1)),查询效率低(需遍历链表,O(n))。
2.2 实战代码:List 核心用法(必掌握)
以下代码覆盖List的常用操作(增删改查、遍历),对比ArrayList和LinkedList的用法差异:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
// 1. ArrayList:查询快、增删慢,适合查询频繁的场景
List<String> arrayList = new ArrayList<>();
// 增:添加元素(允许重复)
arrayList.add("zhangsan");
arrayList.add("zhangsan"); // 重复元素,正常添加,与Set的核心区别
arrayList.add("lisi");
// 查:根据索引获取元素(ArrayList优势)
String first = arrayList.get(0);
System.out.println("ArrayList第一个元素:" + first); // 输出:zhangsan
// 改:修改指定索引的元素
arrayList.set(1, "wangwu");
// 删:删除指定元素(需遍历,效率低)
arrayList.remove("lisi");
// 遍历:for循环(适合ArrayList)
System.out.println("ArrayList遍历:");
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
// 2. LinkedList:增删快、查询慢,适合增删频繁的场景
List<String> linkedList = new LinkedList<>();
linkedList.add("zhencong");
linkedList.add("java");
// 头部添加、尾部添加(LinkedList优势)
((LinkedList<String>) linkedList).addFirst("first");
((LinkedList<String>) linkedList).addLast("last");
// 头部删除、尾部删除
((LinkedList<String>) linkedList).removeFirst();
((LinkedList<String>) linkedList).removeLast();
// 遍历:增强for循环(通用)
System.out.println("LinkedList遍历:");
for (String s : linkedList) {
System.out.println(s);
}
}
}
2.3 应用场景(落地实战)
ArrayList:首页数据展示、分页查询结果存储、频繁查询的列表(如用户订单列表);
LinkedList:消息队列、栈、队列(如任务队列)、频繁增删的列表(如实时消息列表);
通用场景:需要按插入顺序存储、允许重复元素的所有场景(如用户浏览记录)。
2.4 易错点与避坑指南(面试高频)
坑点1:ArrayList 扩容机制——默认初始容量10,扩容时按1.5倍扩容,频繁扩容会浪费内存,建议初始化时指定容量(如 new ArrayList<>(100));
坑点2:LinkedList 不要用for循环遍历——需每次遍历到指定索引,效率极低,优先用增强for或迭代器;
坑点3:List 的 remove 方法——remove(int index) 是删除索引,remove(Object obj) 是删除元素,避免混淆(如 list.remove(1) 是删除索引1,不是元素1)。
三、Set 集合:无序不重复,去重首选
3.1 核心特性与底层结构
Set 是无序集合,元素不可重复,核心实现类有两个:HashSet、TreeSet,底层结构决定了其核心特性:
HashSet:底层基于哈希表(HashMap)实现,无序、去重,查询、增删效率高(O(1));
TreeSet:底层基于红黑树实现,有序(自然排序或自定义排序)、去重,查询、增删效率 O(log n)。
核心重点:Set 去重的原理——依赖元素的 equals() 和 hashCode() 方法,两个方法必须同时重写,否则去重失效。
3.2 实战代码:Set 去重特性与核心用法
以下代码对比HashSet和TreeSet的用法,重点演示去重逻辑,贴合实际开发场景:
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetDemo {
public static void main(String[] args) {
// 1. HashSet:无序、自动去重(开发最常用,效率高)
Set<String> hashSet = new HashSet<>();
// 添加元素(重复元素自动过滤)
hashSet.add("zhangsan");
hashSet.add("zhangsan"); // 重复元素,不会被添加
hashSet.add("lisi");
hashSet.add("wangwu");
// 遍历:无序(输出顺序与添加顺序无关)
System.out.println("HashSet(无序去重):");
for (String s : hashSet) {
System.out.println(s);
}
// 判断元素是否存在(HashSet优势,O(1)效率)
boolean exists = hashSet.contains("lisi");
System.out.println("HashSet中是否存在lisi:" + exists); // 输出:true
// 2. TreeSet:有序、自动去重(需排序场景使用)
Set<String> treeSet = new TreeSet<>();
treeSet.add("zhangsan");
treeSet.add("zhangsan"); // 重复元素,自动过滤
treeSet.add("lisi");
treeSet.add("wangwu");
// 遍历:有序(默认按自然字典序排序)
System.out.println("TreeSet(有序去重):");
for (String s : treeSet) {
System.out.println(s);
}
// 自定义排序(如按字符串长度排序)
Set<String> customTreeSet = new TreeSet<>((o1, o2) -> o1.length() - o2.length());
customTreeSet.add("apple");
customTreeSet.add("banana");
customTreeSet.add("pear");
System.out.println("TreeSet(自定义排序):" + customTreeSet);
}
}
3.3 应用场景(落地实战)
HashSet:数据去重(如用户浏览记录去重、活动参与名单去重)、快速判断元素是否存在(如判断用户是否已报名);
TreeSet:有序去重场景(如排行榜、按时间排序的去重数据、按规则排序的列表);
通用场景:所有需要去重、无需保证顺序(或需要指定顺序)的场景。
3.4 易错点与避坑指南(面试高频)
坑点1:HashSet 去重失效——未重写元素的 equals() 和 hashCode() 方法,导致无法判断元素是否重复;
坑点2:TreeSet 排序异常——元素未实现 Comparable 接口,且未指定自定义比较器,会抛出 ClassCastException;
坑点3:Set 无序误解——HashSet 是“无序”(不保证插入顺序),不是“乱序”,TreeSet 是有序的,需区分两者差异。
四、Map 映射:键值对存储,快速查询首选
4.1 核心特性与底层结构
Map 是键值对集合,核心特性:键唯一、值可重复,键和值均为引用类型,核心实现类是 HashMap(开发最常用),底层结构如下:
HashMap:底层基于哈希表实现(JDK1.8 后是数组+链表+红黑树),查询、增删效率高(O(1));
核心逻辑:通过键的 hashCode() 计算存储位置,通过 equals() 判断键是否重复,重复则覆盖值。
4.2 实战代码:Map 核心用法+对象作为键
以下代码覆盖Map的常用操作,重点演示“自定义对象作为键”的场景(开发高频,面试必问):
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
// 自定义对象作为Map的键(必须重写equals和hashCode)
class UserKey {
private String name;
private Integer id;
public UserKey(String name, Integer id) {
this.name = name;
this.id = id;
}
// getter方法
public String getName() { return name; }
public Integer getId() { return id; }
// 重写equals和hashCode:保证相同对象的hashCode相等,equals返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserKey userKey = (UserKey) o;
return Objects.equals(name, userKey.name) && Objects.equals(id, userKey.id);
}
@Override
public int hashCode() {
return Objects.hash(name, id);
}
// 重写toString,方便打印
@Override
public String toString() {
return "UserKey{name='" + name + "', id=" + id + "}";
}
}
public class MapDemo {
public static void main(String[] args) {
// 1. 普通Map:键为String,值为String
Map<String, String> map = new HashMap<>();
// 增/改:key存在则覆盖值,不存在则添加
map.put("zhencong", "后端开发");
map.put("zhencong", "分布式开发"); // 相同key,覆盖值
map.put("lisi", "前端开发");
// 查:根据key获取值
String value = map.get("zhencong");
System.out.println("key=zhencong的值:" + value); // 输出:分布式开发
// 删:根据key删除键值对
map.remove("lisi");
// 遍历:遍历所有键值对(开发常用)
System.out.println("普通Map遍历:");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
}
// 2. 自定义对象作为Map的键(面试高频场景)
Map<UserKey, String> userMap = new HashMap<>();
UserKey key1 = new UserKey("zhangsan", 1);
UserKey key2 = new UserKey("zhangsan", 1); // 与key1是同一个对象(重写了equals和hashCode)
UserKey key3 = new UserKey("lisi", 2);
userMap.put(key1, "Java工程师");
userMap.put(key2, "Redis工程师"); // 覆盖值(key1和key2相等)
userMap.put(key3, "MySQL工程师");
// 遍历自定义key的Map
System.out.println("自定义key的Map遍历:");
for (Map.Entry<UserKey, String> entry : userMap.entrySet()) {
System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
}
System.out.println("userMap的大小:" + userMap.size()); // 输出:2(key1和key2合并)
}
}
关键提醒:自定义对象作为Map的键时,必须同时重写 equals() 和 hashCode() 方法,否则Map无法判断键是否重复,会导致相同对象被当作不同键存储,造成数据异常。
4.3 实战拓展:Map 实现去重+排序(贴合业务场景)
实际开发中,常遇到“去重+排序”的场景(如用户金额去重,按金额排序),以下代码结合Map和List实现,贴合真实业务:
public class User {
private String name;
private int money;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Test1 {
public static void main(String[] args) {
User user = new User();
user.setName("ryn");
user.setMoney(100);
User user2 = new User();
user2.setName("wangwu");
user2.setMoney(10);
User user3 = new User();
user3.setName("ryn");
user3.setMoney(1000);
List<User> users = new ArrayList<>();
users.add(user);
users.add(user2);
users.add(user3);
check(users);
}
private static void check(List<User> users) {
//分组
//这个map留的是所有用户去重后的金额,key是用户名,value是金额
Map<String, Integer> map = new HashMap<>();
for (User user : users) {
Integer money = map.get(user.getName());
if (money == null) {
map.put(user.getName(), user.getMoney());
continue;
}
if (money > user.getMoney()) {
continue;
}
map.put(user.getName(), user.getMoney());
}
//按照金额进行排序
List<Map.Entry<String, Integer>> list = new ArrayList<>(map.entrySet());
sort(list);
for (Map.Entry<String, Integer> stringIntegerEntry : list) {
System.out.println(stringIntegerEntry.getKey() + "----" + stringIntegerEntry.getValue());
}
}
private static void sort(List<Map.Entry<String, Integer>> list) {
for (int i = 0; i < list.size() - 1; i++) {
for (int j = 0; j < list.size() - 1 - i; j++) {
if (list.get(j).getValue() < list.get(j + 1).getValue()) {
Map.Entry<String, Integer> temp = list.get(j);
list.set(j, list.get(j + 1));
list.set(j + 1, temp);
}
}
}
}
}

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Test2 {
public static void main(String[] args) {
User user1 = new User();
user1.setName("zhencong");
user1.setMoney(100);
User user2 = new User();
user2.setName("wangwu");
user2.setMoney(10);
User user3 = new User();
user3.setName("zhencong");
user3.setMoney(1000);
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
//统计每个人的金额总数
//按梯度输出人员数量以及人名 0-100,101-1000,1001-10000
// 1. 统计每个人的金额总数
Map<String, Integer> map1 = new HashMap<>();
for (User user : users) {
String name = user.getName();
int money = user.getMoney();
// 累加金额:如果已存在则累加,不存在则初始化为当前金额
map1.put(name, map1.getOrDefault(name, 0) + money);
}
// 输出每个人的总金额
for (Map.Entry<String, Integer> every : map1.entrySet()) {
System.out.println("姓名:" + every.getKey() + ",总金额:" + every.getValue());
}
// 2. 按梯度分组:0-100,101-1000,1001-10000
// 定义梯度分组的容器:key为梯度描述,value为该梯度下的用户列表
Map<String, List<String>> group = new HashMap<>();
// 初始化三个梯度分组
group.put("0-100", new ArrayList<>());
group.put("101-1000", new ArrayList<>());
group.put("1001-10000", new ArrayList<>());
// 遍历统计后的总金额,按梯度分组
for (Map.Entry<String, Integer> entry : map1.entrySet()) {
String name = entry.getKey();
int totalMoney = entry.getValue();
if (totalMoney >= 0 && totalMoney <= 100) {
group.get("0-100").add(name);
} else if (totalMoney >= 101 && totalMoney <= 1000) {
group.get("101-1000").add(name);
} else if (totalMoney >= 1001 && totalMoney <= 10000) {
group.get("1001-10000").add(name);
}
}
// 按梯度输出人员数量和人名
System.out.println("\n=== 按金额梯度分组结果 ===");
for (Map.Entry<String, List<String>> entry : group.entrySet()) {
String gradient = entry.getKey();
List<String> names = entry.getValue();
System.out.println("梯度 " + gradient + ":");
System.out.println(" 人员数量:" + names.size());
System.out.println(" 人员名单:" + (names.isEmpty() ? "无" : String.join("、", names)));
}
}
}

4.4 应用场景(落地实战)
普通Map:配置信息存储、用户信息缓存、键值对查询(如根据用户ID查询用户名);
自定义对象作为键:复杂场景的键值对存储(如根据用户对象查询用户详情、根据订单对象查询订单状态);
去重+排序:用户排行榜、数据统计去重(如统计每个用户的最大消费金额并排序)。
4.5 易错点与避坑指南(面试高频)
坑点1:HashMap 键为null——HashMap 允许键为null(仅一个),值可多个null,Hashtable 不允许键和值为null;
坑点2:HashMap 线程不安全——多线程环境下修改HashMap会导致死循环,需使用 ConcurrentHashMap;
坑点3:Map 遍历方式选择——遍历所有键值对用 entrySet()(效率高),仅遍历键用 keySet(),仅遍历值用 values();
坑点4:hashCode 和 equals 的关系——同一个对象的 hashCode 一定相等;hashCode 相等的两个对象,equals 不一定相等(哈希冲突);equals 相等的两个对象,hashCode 必须相等。
五、三大集合对比总结(必背表格)
|
集合类型 |
核心特点 |
底层结构 |
典型实现类 |
典型应用场景 |
|
List |
有序、可重复、支持索引访问 |
ArrayList(数组)、LinkedList(双向链表) |
ArrayList、LinkedList |
分页查询、消息队列、浏览记录、有序列表 |
|
Set |
无序、不重复、支持元素存在判断 |
HashSet(哈希表)、TreeSet(红黑树) |
HashSet、TreeSet |
数据去重、元素存在判断、有序排行榜 |
|
Map |
键唯一、值可重复、键值对映射 |
HashMap(数组+链表+红黑树) |
HashMap、ConcurrentHashMap |
配置存储、快速查询、去重+排序、对象映射 |
六、面试高频题(必背)
6.1 List 相关
Q1:ArrayList 和 LinkedList 的区别?如何选型? A1:① 底层结构:ArrayList 是数组,LinkedList 是双向链表;② 性能:ArrayList 查询快、增删慢,LinkedList 增删快、查询慢;③ 选型:查询频繁用 ArrayList,增删频繁用 LinkedList。
Q2:ArrayList 的扩容机制是什么? A2:默认初始容量10,当元素数量超过容量时,按1.5倍(oldCapacity >> 1)扩容,扩容时会创建新数组,将原数组元素复制到新数组,频繁扩容会浪费内存。
6.2 Set 相关
Q1:HashSet 去重的原理是什么? A1:依赖元素的 equals() 和 hashCode() 方法:① 先通过 hashCode() 计算存储位置;② 若该位置无元素,直接存储;③ 若有元素,通过 equals() 判断是否相同,相同则不存储,不同则存储(哈希冲突处理)。
Q2:HashSet 和 TreeSet 的区别? A2:① 有序性:HashSet 无序,TreeSet 有序;② 底层结构:HashSet 是哈希表,TreeSet 是红黑树;③ 效率:HashSet 增删查效率 O(1),TreeSet 是 O(log n);④ 去重:两者都支持去重,TreeSet 额外支持排序。
6.3 Map 相关
Q1:HashMap 的底层结构(JDK1.8)? A1:底层是数组+链表+红黑树:① 数组是哈希桶,每个桶存储链表(哈希冲突时);② 当链表长度超过8,且数组长度≥64时,链表转为红黑树;③ 当红黑树节点数少于6时,转回链表,平衡性能和内存。
Q2:为什么自定义对象作为 Map 的键,必须重写 equals 和 hashCode? A2:① Map 判断键是否重复,先比较 hashCode,再比较 equals;② 若不重写,会使用 Object 类的默认方法,默认 hashCode 是对象的内存地址,即使两个对象内容相同,也会被当作不同键,导致去重失效。
七、总结(开发/面试必备)
本文围绕Java三大核心集合(List、Map、Set),结合实战代码,完整讲解了用法、底层原理、应用场景和易错点,核心要点总结如下:
选型原则:有序可重复用 List,去重用 Set,键值对存储用 Map,根据性能需求选择具体实现类;
核心重点:Set 和 Map 的去重,依赖 equals() 和 hashCode() 方法,必须同时重写;
性能优化:ArrayList 初始化指定容量,LinkedList 避免for循环遍历,HashMap 避免多线程修改;
面试高频:三大集合的区别、底层结构、扩容机制、去重原理,是后端面试必问,务必掌握。
看完这一篇,Java集合的核心用法和避坑点基本全覆盖,日常开发遇到集合相关问题,直接对照查阅即可,建议收藏备用!
更多推荐




所有评论(0)