set() 是设成某值,add() 是加减若干单位;set() 不触发进位,add() 自动处理溢出;设每月1日需用 set(Calendar.DAY_OF_MONTH, 1);getActualMaximum() 比硬编码31更可靠;getTimeInMillis() 需先 get() 触发重算;Calendar 非线程安全,须每次新建或用 ThreadLocal;Java 8+ 应优先使用 LocalDateTime/ZonedDateTime。

Calendar 的 set() 和 add() 到底该用哪个
直接说结论:set() 是“设成某值”,add() 是“加减若干单位”。混淆两者是 Java 日期计算中最常见的逻辑错误来源。
比如想把一个 Calendar 对象的月份设为 12 月(即 Calendar.DECEMBER),用 set(Calendar.MONTH, 11) 没问题;但若误用 add(Calendar.MONTH, 11),就会在当前月份基础上加 11 个月,结果完全不可控。
-
set()不会触发自动进位或借位:调用cal.set(Calendar.DAY_OF_MONTH, 32)后,cal.get(Calendar.DAY_OF_MONTH)可能返回 1(下月1号),这取决于底层实现和时区,行为不直观 -
add()会自动处理溢出:如 1 月 31 日 + 1 个月 → 自动变成 2 月 28 日(或 29 日) - 如果要“清零某字段再设新值”,不能只靠
set();例如想设成每月 1 日,得先set(Calendar.DAY_OF_MONTH, 1),而不是set(Calendar.DATE, 1)(二者等价,但别混用别名)
为什么 getActualMaximum(Calendar.DAY_OF_MONTH) 比直接写 31 更可靠
因为不是每个月都有 31 天。硬编码 31 在 4 月、6 月、9 月、11 月或 2 月会出错——尤其是跨年、跨闰年时。
getActualMaximum() 返回当前 Calendar 所处月份的实际最大天数,它考虑了年份、月份、甚至时区(极少数情况下影响月末判断)。
立即学习“Java免费学习笔记(深入)”;
Calendar cal = Calendar.getInstance(); cal.set(2024, Calendar.FEBRUARY, 15); // 注意:月份从 0 开始 int lastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); // 返回 29 cal.set(Calendar.DAY_OF_MONTH, lastDay); // 安全设为当月最后一天
- 别用
getMaximum()——它返回该字段理论最大值(如DAY_OF_MONTH总是返回 31) - 调用
getActualMaximum()前,确保Calendar的年/月已明确设定,否则可能基于当前时间,导致意外结果 - 这个方法不修改日历状态,只读取,可放心多次调用
Calendar.getTimeInMillis() 和 new Date().getTime() 的精度陷阱
两者都返回毫秒数,但关键区别在于:前者依赖 Calendar 内部字段是否已“计算完成”。如果刚调用 set() 就立刻取时间戳,可能拿到过期缓存值。
典型表现:设了年月日,但没设时分秒,getTimeInMillis() 返回的时间却包含旧的时分秒甚至毫秒值,导致时间偏移。
- 安全做法是调用
cal.getTimeInMillis()前,先执行cal.get(Calendar.YEAR)或任意get()——这会强制触发内部重算(computeTime()) - 更稳妥的是直接调用
cal.getTime().getTime(),getTime()方法内部已保证时间同步 - 注意:
Calendar默认时区是 JVM 启动时确定的,跨时区操作必须显式cal.setTimeZone(TimeZone.getTimeZone("UTC"))
Calendar 的线程不安全性怎么避坑
Calendar 实例不是线程安全的。多个线程共用同一个实例,同时调用 set()、add() 或 get(),会导致字段错乱、时间计算错误,且极难复现。
这不是“尽量避免共享”的建议,而是“必须隔离”的硬性要求。
- 不要将
Calendar设为静态变量或单例成员 - 每次需要时用
Calendar.getInstance()创建新实例(开销极小) - 如果频繁创建,可用
ThreadLocal缓存,但需注意内存泄漏风险(尤其在 Web 容器中未清理) - Java 8+ 强烈建议迁移到
LocalDateTime/ZonedDateTime——它们不可变、无状态、天然线程安全
Calendar 类的设计初衷是作为“可变日期计算器”,但它把状态管理、时区、历法规则全揉在一起,稍有不慎就掉进坑里。真正复杂的日期逻辑,别硬扛,尽早切到 java.time 包。










