新项目应直接使用java.time包;date构造器和部分方法因年份偏移、月份从0开始等设计缺陷被弃用;calendar必须用getinstance()获取实例,且线程不安全。

Java 里 Date 和 Calendar 都是过时(deprecated)但仍在大量旧代码中出现的类,它们的设计缺陷多、线程不安全、API 反直觉,**新项目应直接用 java.time 包(如 LocalDateTime、ZonedDateTime)**;但如果要维护老代码或对接遗留系统,必须清楚它们怎么错、怎么绕。
为什么 Date 的构造函数和部分方法被标记为 deprecated
Date 最初设计混乱:它的 Date(int, int, int) 等带参数构造器把年份当作“距 1900 年的偏移”,月份从 0 开始(0=Jan),日却从 1 开始——三者不统一,极易出错。例如:new Date(123, 0, 1) 表示的是 2023 年 1 月 1 日(不是 123 年),而 new Date(123, 1, 1) 是 2023 年 2 月 1 日。
- 所有带参数的
Date构造器、getYear()、getMonth()、getDate()、setYear()等方法在 Java 1.1 后就被标记为@Deprecated - 唯一推荐的用法是
new Date()(当前时间)或new Date(long)(毫秒时间戳),再配合SimpleDateFormat格式化/解析 - 注意:
Date.toString()输出的是 JVM 默认时区的时间,不是 UTC,且格式固定不可控
为什么 Calendar 必须用 getInstance() 而不能 new
Calendar 是抽象类,直接 new Calendar() 编译不通过;它依赖具体子类(如 GregorianCalendar)实现,而子类行为受 locale 和时区影响。JVM 会根据当前 Locale.getDefault() 和 TimeZone.getDefault() 返回最合适的实例。
- 永远用
Calendar.getInstance(),不要写new GregorianCalendar()(除非你明确需要无参默认行为,且能承担 locale 依赖风险) - 设置日期字段必须调用
set(Calendar.YEAR, 2023)等,不能直接赋值;且修改后需调用calendar.getTime()才能拿到生效的Date对象 - 月份仍从 0 开始(
Calendar.JANUARY == 0),但年、日、小时等都是自然数——这是它比Date构造器稍好,但仍易错的关键点 - 线程不安全:多个线程共用一个
Calendar实例会导致状态污染,每次使用前建议clone()或重新getInstance()
Date 和 Calendar 互转的坑在哪
表面上转换很简单:calendar.setTime(date) 或 date = calendar.getTime(),但背后有隐含时区和精度问题。
立即学习“Java免费学习笔记(深入)”;
-
Calendar存储的是“带时区的时间点”,而Date本质只是毫秒数(UTC 时间轴上的一个点);calendar.getTime()返回的Date对象本身不含时区信息,但它的toString()会按Calendar当前时区格式化输出 - 如果只设了年月日(如
cal.set(2023, Calendar.JANUARY, 1)),未设时分秒,默认为 00:00:00.000 —— 但这个“00:00”是Calendar当前时区的 0 点,不是 UTC 0 点 - 跨时区转换时,错误地先
setTime(date)再set(Calendar.HOUR_OF_DAY, 12),可能因时区偏移导致日期跳变(比如从东京时区切到纽约,同个毫秒数显示成不同日期)
如果必须用,怎么最小化风险
没有银弹,只能靠约束和封装。老系统改不动时,至少做到:
- 所有日期创建统一走
Calendar.getInstance(TimeZone.getTimeZone("UTC")),显式指定时区,避免依赖默认值 - 读写数据库或网络传输时,一律用
long毫秒值(date.getTime())或 ISO 8601 字符串(用SimpleDateFormat配合setTimeZone(TimeZone.getTimeZone("UTC"))) - 禁止在业务逻辑里用
date.getYear()这类 deprecated 方法;可用Calendar中转,例如cal.setTime(date); int year = cal.get(Calendar.YEAR); - 任何涉及“今天”“月初”“上个月”的计算,必须用
Calendar.add()/roll(),别手动加减毫秒数(会忽略闰秒、夏令时等)
真正麻烦的从来不是语法,而是时区、夏令时、历史日历变更(比如 1582 年 10 月跳过 10 天)这些隐藏规则——java.time 把它们封装进类型和 API 里,而 Date/Calendar 把它们甩给开发者自己查文档、试边界值。










