返回专题首页

Java 专题

集合与常用工具类:List、Set、Map、String 与 java.time

Java 日常开发里,真正使用频率最高的,往往不是某个框架注解,而是集合、字符串和时间处理这些基础工具。它们看起来“很基础”,也正因为太基础,所以最容易被低估。结果就是:

Java 专题第 06 篇 / 26 篇6 分钟

Java 日常开发里,真正使用频率最高的,往往不是某个框架注解,而是集合、字符串和时间处理这些基础工具。它们看起来“很基础”,也正因为太基础,所以最容易被低估。结果就是:

  • 集合选错,性能和语义一起出问题;
  • 字符串处理随意,埋下比较、拼接和编码隐患;
  • 时间处理偷懒,最后在时区、格式化和边界条件上反复翻车。

所以这一节真正要做的,不是背 API,而是建立一套选型心智:什么场景该用什么容器,为什么同样都是“存一批数据”却不能随意替换,以及这些工具放到真实业务里最容易在哪些地方出错。

集合为什么不是“能装数据就行”?

很多初学者对集合的理解只停留在:

  • List 能放重复元素;
  • Set 不能重复;
  • Map 是键值对。

这些当然没错,但只够应付最表层的问题。真实项目里,你更应该先问的是:

  • 数据是否有顺序?
  • 是否允许重复?
  • 是否需要按索引访问?
  • 是否需要按 key 快速定位?
  • 是否需要排序?
  • 是否会在并发场景共享?

也就是说,集合选择本质上不是选一个类名,而是在表达数据语义和访问模式。容器不是实现细节,它其实是业务建模的一部分。

List、Set、Map 到底该怎么选?

List

List 适合表达“有顺序、允许重复”的数据,比如:

  • 商品列表;
  • 日志记录;
  • 页面步骤流;
  • 接口返回的有序结果集合。

最常见的实现通常是 ArrayList,因为它在大多数读多写少、按索引访问较频繁的场景里都很合适。很多人一开始会觉得 LinkedList 好像也很常见,但在现代业务开发里,它远没有很多人想象得那么万能。尤其当你真正关心的是队列语义时,通常更应该看 DequeArrayDeque,而不是机械地把 LinkedList 当作“插入删除快”的默认答案。

Set

Set 的核心价值并不只是“换一个容器”,而是在告诉读代码的人:这批数据在语义上不能重复。像这些场景通常更适合 Set:

  • 用户角色集合;
  • 标签集合;
  • 已处理任务 ID 集合;
  • 去重后的编码集合。

常见实现里:

  • HashSet 强调快速查找;
  • LinkedHashSet 在去重的同时保留插入顺序;
  • TreeSet 适合有天然排序规则的场景,但也意味着更高成本。

真正重要的不是你记住了几个类,而是你能否让容器本身就体现数据规则。

Map

Map 表达的是“通过 key 组织数据”。当你需要根据某种标识快速找到某个对象时,Map 往往是最自然的结构。比如:

  • 按用户 ID 查用户信息;
  • 按配置项编码查配置值;
  • 按状态码映射状态文案;
  • 按商品编码查商品对象。

最常见的实现是 HashMap,而当你需要稳定插入顺序时,可以考虑 LinkedHashMap;当你需要按 key 排序时,TreeMap 更自然。

真正的判断重点不是“背容器”,而是先问:这批数据到底应该怎样被组织、怎样被查找。

equals 和 hashCode 为什么会直接影响集合行为?

这是集合使用里最容易被忽略、但实际影响极大的问题。只要对象进入哈希结构,比如 HashSetHashMap,它的相等性规则就不再是“感觉差不多”,而是必须由你明确告诉系统:

  • 什么情况下两个对象算相等;
  • 相等对象是否会得到一致的 hashCode。

只要这里定义不清楚,你就很容易遇到这些问题:

  • Set 去重失败;
  • Map key 查不回来;
  • 明明内容一样,对象却被当成不同元素;
  • 某些行为在测试里正常,换一组对象后就异常。

所以集合使用绝不是“会 new 一个容器”就够了,很多时候它要求你先把对象相等性在业务上定义清楚。

String 为什么也是高频设计问题?

很多人会低估 String,因为它看起来太简单了。但它其实是最容易被反复使用、也最容易被误用的基础类之一。

真正值得建立的几个认知包括:

  • String 是不可变的;
  • 比较字符串内容必须用 equals
  • 高频拼接要注意 StringBuilder
  • 文本处理往往还会牵涉编码、空白字符、国际化和格式规范。

在项目里,字符串处理并不只是“拼接文案”那么简单。它常常和这些场景强相关:

  • 日志输出;
  • SQL / 参数构造;
  • 文件路径;
  • 序列化和反序列化;
  • 导出内容;
  • 接口签名和校验。

越是基础类,越不能想当然。

为什么现在应该优先使用 java.time?

Java 旧版时间 API 最大的问题,不是“难记”,而是语义模糊、容易误用。现代 Java 项目更推荐 java.time,因为它把不同时间概念拆得更清楚了。

你至少应该建立这样的区分:

  • LocalDate 只表示日期;
  • LocalTime 只表示时间;
  • LocalDateTime 表示不带时区的日期时间;
  • Instant 表示绝对时间点;
  • ZonedDateTime / OffsetDateTime 处理时区问题;
  • Duration / Period 表达时间间隔。

这套模型看起来比旧 API 更细,但其实是更安全。因为时间处理最怕“概念没分清”:

  • 展示时间和存储时间混在一起;
  • 本地时间和绝对时间混在一起;
  • 不同系统时区差异没考虑;
  • 业务结算日、创建时间、更新时间全都用一个类型糊过去。

一旦类型选对,很多错能在设计阶段就被挡掉。

放到项目里,这些基础工具最常落在哪些判断上?

真实项目里,集合、字符串和时间处理几乎无处不在。常见判断包括:

  • 某个返回结构到底该用 List、Set 还是 Map;
  • 某个对象作为 Map key 是否合理;
  • 某段文本是简单拼接,还是应该抽成模板;
  • 时间字段应该表达本地日期、完整时间,还是绝对时间点;
  • 某些容器是否会进入并发场景;
  • 某些集合是否真的需要顺序保证。

你会发现,这些问题都不是“API 怎么调”,而是“语义和边界怎么表达”。

最容易踩的坑

这一节里最常见的问题通常包括:

  • 不区分访问模式,所有场景都默认 ArrayListHashMap
  • 对象进 HashSet / HashMap,却没有正确理解相等性规则;
  • == 比较字符串;
  • 循环里大量字符串拼接却不关注中间对象开销;
  • 时间处理继续沿用模糊旧心智,不区分日期、时间、时间点和时区。

这些问题之所以常见,是因为它们都出现在“太基础以至于容易想当然”的地方。

总结

集合与常用工具类从来不是“入门阶段学完就结束”的东西,它们几乎每天都在参与业务代码的表达。真正重要的不是你记住了多少 API,而是你能不能根据数据语义、访问模式和边界要求做正确选择。只要这层选型心智建立起来,后面的异常、泛型、并发和框架学习都会更容易落到实处。