开篇:数组的"三宗罪"与集合的登场
第四章讲数组时埋了一句话:数组长度创建时定死,之后不能变。做设备台账管理时这个限制立刻现形:
-
新设备入库,数组满了怎么办?——长度定死,加不了;
-
按设备编号查某台设备,数组怎么查?——只能挨个遍历,笨拙;
-
一堆设备编号里有重复,想快速去重?——数组没有现成的办法。
Java 的答案在 java.util 包里:集合(Collection)家族。日常开发 90% 的场景,三个类就够用:ArrayList(会生长的数组)、HashMap(键值对字典)、HashSet(自动去重的袋子)。先看一个完整的"设备台账"程序,三个主角全部登场:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class DeviceLedgerApp {
public static void main(String[] args) {
// ===== 1. ArrayList:设备清单,随用随加 =====
ArrayList<String> devices = new ArrayList<>();
devices.add("3号皮带输送机");
devices.add("中央水泵");
devices.add("辅扇2号");
devices.add("中央水泵"); // 手滑重复录入
System.out.println("清单条数:" + devices.size());
System.out.println("第一台:" + devices.get(0));
// ===== 2. HashSet:看看实际有多少种设备(自动去重)=====
HashSet<String> unique = new HashSet<>(devices);
System.out.println("去重后种数:" + unique.size());
// ===== 3. HashMap:编号 → 设备详情,按号秒查 =====
HashMap<String, String> ledger = new HashMap<>();
ledger.put("PD-003", "3号皮带输送机 | 东翼大巷 | 运行中");
ledger.put("SB-011", "中央水泵 | -580m泵房 | 检修中");
ledger.put("FJ-102", "辅扇2号 | 三采区回风巷 | 运行中");
// 按编号查
String info = ledger.get("SB-011");
System.out.println("SB-011 → " + info);
// 改状态:同 key 再 put 就是覆盖
ledger.put("SB-011", "中央水泵 | -580m泵房 | 运行中");
// 遍历台账:打印所有编号和详情
System.out.println("===== 设备台账 =====");
for (Map.Entry<String, String> entry : ledger.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
// 只关心编号时
for (String code : ledger.keySet()) {
System.out.println("编号:" + code);
}
}
}
输出:
清单条数:4
第一台:3号皮带输送机
去重后种数:3
SB-011 → 中央水泵 | -580m泵房 | 检修中
===== 设备台账 =====
PD-003 : 3号皮带输送机 | 东翼大巷 | 运行中
SB-011 : 中央水泵 | -580m泵房 | 运行中
FJ-102 : 辅扇2号 | 三采区回风巷 | 运行中
编号:PD-003
编号:SB-011
编号:FJ-102
三个类、一套增删改查、两种 Map 遍历,全在这了。逐个讲。
第〇部分:先认识尖括号——泛型(Generics)
ArrayList<String> 里的 <String> 是泛型:告诉编译器"这个清单里只装 String"。它的价值有两个:
-
编译期排错:
devices.add(123)直接编译报错,错类型混不进去; -
取出来不用强转:
devices.get(0)返回的就是 String,直接用。
本系列对泛型的要求就到"会用"为止——会在集合上声明、能看懂别人的泛型代码,足够。怎么自己写泛型类/泛型方法属于进阶内容,日常业务代码极少需要自己写,本系列不讲。
HashMap 有两个尖括号:<String, String> 前者是键(key)的类型,后者是值(value)的类型。键值类型随意搭配,HashMap<String, Worker>(编号 → 工人对象)在真实项目里比比皆是。
第一部分:ArrayList——会生长的数组
最常用的集合,没有之一。内部就是数组,但自动扩容,你只管往里塞。
高频方法一张表:
| 方法 | 作用 | 例子 |
|---|---|---|
add(e) | 尾部追加 | devices.add("水泵") |
get(i) | 按下标取 | devices.get(0) |
size() | 元素个数 | 注意是方法,对应数组的 length 属性 |
remove(i) / remove(对象) | 按下标/按内容删 | devices.remove(0) |
contains(e) | 是否包含 | devices.contains("水泵") |
isEmpty() | 是否为空 | 判空首选,比 size() == 0 可读性好 |
set(i, e) | 按下标替换 | devices.set(0, "新设备") |
遍历用第三章学的增强 for,天作之合:
for (String name : devices) {
System.out.println(name);
}
ArrayList vs 数组,怎么选? 一句话:日常业务用 ArrayList,数组只在长度固定、追求极限性能、或与老 API 打交道时用。你职业生涯里写的 ArrayList 会比数组多一个数量级。
三个长度家族别记混(软考爱考):数组 arr.length(属性)、字符串 s.length()(方法)、集合 list.size()(方法)。
第二部分:HashMap——键值对字典
HashMap<String, String>:一把 key 对应一个 value,按 key 查找是 Map 的看家本领——不用遍历,get("SB-011") 一步到位。
高频方法一张表:
| 方法 | 作用 |
|---|---|
put(k, v) | 存入;key 已存在则覆盖旧值 |
get(k) | 按 key 取;key 不存在返回 null |
containsKey(k) | key 是否存在 |
remove(k) | 按键删除 |
keySet() | 所有 key 的集合 |
values() | 所有 value 的集合 |
entrySet() | 所有"键值对"的集合 |
size() | 键值对个数 |
遍历的两种主流姿势
要 key 也要 value(最常用)——entrySet:
for (Map.Entry<String, String> entry : ledger.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
Map.Entry 就是"一个键值对"的类型,遍历它一次拿到两样东西,效率最高。
只要 key——keySet:
for (String code : ledger.keySet()) { ... }
两个必知特性
-
key 唯一,value 随便。同 key 再 put 是覆盖——案例里水泵"检修中"被改成"运行中"就靠这个特性。反过来想:更新 Map 不用先查再改,直接 put。
-
get一个不存在的 key 返回 null,不报错。接着用这个 null 就会喜提空指针异常:
String info = ledger.get("XX-999");
System.out.println(info.length()); // NullPointerException!
稳妥姿势:
String info = ledger.get("XX-999");
if (info != null) {
System.out.println(info.length());
}
或者用 ledger.getOrDefault("XX-999", "无此设备")——查不到给默认值,防空指针的神器。
-
HashMap 不保证顺序。遍历出来的顺序不等于 put 的顺序(案例输出恰好一致是巧合)。需要按插入顺序遍历用
LinkedHashMap,知道有这个东西即可。
第三部分:HashSet——自动去重的袋子
HashSet 是最单纯的集合:装元素、自动去重、不保顺序。案例里一句完成去重:
HashSet<String> unique = new HashSet<>(devices); // 构造时传入 List,重复自动消失
高频方法就几个:add(重复时静默忽略)、contains、remove、size。判断"某个编号在不在黑名单里"这种纯存在性场景,HashSet 的 contains 比 ArrayList 快得多,是标准选择。
三个主角的选型口诀:
按位置存取一串东西 → ArrayList;按 key 查 value → HashMap;只关心"有没有、去重" → HashSet。
第四部分:遍历中删除——集合最大的坑
先看这段"看似合理"的代码:
// 想删掉清单里所有"检修中"的设备——错误示范!
for (String name : devices) {
if (name.equals("中央水泵")) {
devices.remove(name); // 运行抛 ConcurrentModificationException
}
}
增强 for 遍历的过程中直接改集合结构,Java 会立刻抛 ConcurrentModificationException 崩溃——遍历和修改必须分开。
标准解法是用迭代器(Iterator),边遍历边通过迭代器自己删除:
import java.util.Iterator;
Iterator<String> it = devices.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.equals("中央水泵")) {
it.remove(); // 通过迭代器删,安全
}
}
迭代器的三板斧:hasNext()(还有吗)、next()(取下一个)、remove()(删掉刚取的那个)。这是本章讲的"第三种遍历方式",日常不如增强 for 常见,但"遍历中删除"这个场景它是唯一正解,必须会。
常踩的坑(排雷清单)
-
遍历中直接 remove/add:ConcurrentModificationException,用迭代器的 remove。
-
get 完不判 null 就用:Map 查无此键返回 null,防空指针要么判 null 要么 getOrDefault。
-
三个长度记混:数组
length、字符串length()、集合size()。 -
以为 ArrayList 能装 int:
ArrayList<int>编译报错!泛型必须是引用类型,装整数用包装类ArrayList<Integer>——这就是第十章开场要讲的包装类。 -
指望 HashMap/HashSet 有序:不保序。要插入顺序用 LinkedHashMap。
-
用 == 比较集合里的字符串:从集合 get 出来的 String 比较内容照样用 equals——第二章的铁律在集合里依然有效。
本章小结
-
泛型
<String>:编译期排错 + 免强转,会用即可。 -
ArrayList:会生长的数组,增删改查一张表,业务默认选择。
-
HashMap:键值对秒查,key 唯一、put 覆盖、get 查无返 null;遍历首选 entrySet。
-
HashSet:去重与存在性判断的专业户。
-
遍历三姿势:增强 for(主流)、迭代器(遍历中删除的唯一正解)、entrySet(Map 专用)。
-
选型口诀:一串用 List,按键查用 Map,去重用 Set。
差最后一块拼图:为什么 ArrayList<int> 不行、Integer 是什么、"123" 怎么变成 123。下一章系列收官:常用工具类——包装类与自动装箱拆箱、StringBuilder 复盘、Math 与日期时间。学完这章,Java 日常语法版图拼完。
:常用集合——ArrayList、HashMap、HashSet 与遍历&spm=1001.2101.3001.5002&articleId=163634624&d=1&t=3&u=d592ab2d58fc4fff8233b4ac5bdff168)
1617

被折叠的 条评论
为什么被折叠?



