为什么日期构造函数被废弃了,我应该使用什么代替它?

272

我来自C#世界,所以对Java还不是很有经验。刚才Eclipse告诉我Date被弃用了:

Person p = new Person();
p.setDateOfBirth(new Date(1985, 1, 1));

为什么?在这种情况下应该使用什么替代方案?


53
我正在经历类似的学习曲线,也是从C#转向Java。另一个让我遇到麻烦的问题是月份是基于0的系统(从0到11,其中1月= 0,12月= 11),但日期是基于1的系统(从1到31)。请注意这一点! - Paul Sasik
3
@Paul Sasik,是的,但例如有Calendar.JANUARY常量,以及每个月份都有一个对应的常量。 - Diogo
8
@PaulSasik 哈哈,是啊,Java 真是太烦了。不得不从 C# 转到 Java,简直是痛苦不堪。 - cbmeeks
2
可能是 为什么大多数 java.util.Date 方法已被弃用? 的重复提问。 - Thomas
11
C# 程序员用嘲讽的语气对 Java 发表了一些“lol”的评论,但这让我笑了,因为 .Net 借鉴了 Java 出色的时间日期库 Joda-Time,并推出了一个像样的日期时间库 Noda Time。请参考链接:Noda TimeJoda-Time - Basil Bourque
显示剩余6条评论
14个回答

286

java.util.Date类并未被废弃,只有其中一些构造函数和方法被废弃。它被废弃是因为该类的使用方式不能很好地支持国际化。应该使用Calendar类代替:

Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 1988);
cal.set(Calendar.MONTH, Calendar.JANUARY);
cal.set(Calendar.DAY_OF_MONTH, 1);
Date dateRepresentation = cal.getTime();

请查看Java文档中的日期说明:

http://download.oracle.com/javase/6/docs/api/java/util/Date.html


2
这应该是最佳答案。谢谢。 - jordaniac89
1
你所说的“国际化不兼容”是指对于日期,无法为其分配时区吗?谢谢。 - DiveInto
Date类所做的是解析一个字符串,因此现在我们必须对包含年、月和日的字符串进行子字符串操作。这似乎增加了很多额外的麻烦,而大多数情况下并不需要添加如此复杂的逻辑和方法。 - G_V
2
只是补充一下,这将考虑默认时区。如果我们想指定任何其他时区,我们可以使用 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(<timezone id>)); - Vikas Prasad
3
对于Java 8及更高版本,应使用Calendar类代替...如果您正在更改代码,则java.time.*类是更好的选择。 - Stephen C
显示剩余2条评论

138

简短精炼

LocalDate.of( 1985 , 1 , 1 )

…或者…

LocalDate.of( 1985 , Month.JANUARY , 1 )

详情

java.util.Datejava.util.Calendarjava.text.SimpleDateFormat类在Java第一次推出和发展时被匆忙推出。这些类的设计和实现都不太好。改进尝试过,因此您发现了它们的弃用。不幸的是,改进尝试在很大程度上失败了。您应该完全避免使用这些类。在Java 8中,它们被新类所取代。

代码中的问题

java.util.Date既有日期部分,也有时间部分。您在代码中忽略了时间部分。因此,Date类将采用JVM默认时区定义的一天开始时间,并将该时间应用于Date对象。因此,您的代码结果将根据运行的机器或设置的时区而异。可能不是您想要的。

如果您只需要日期而不需要时间部分,例如出生日期,您可能不想使用Date对象。您可能希望仅存储日期字符串,格式为ISO 8601YYYY-MM-DD。或者使用来自Joda-Time的LocalDate对象(参见下文)。

Joda-Time

Java中要学习的第一件事:避免与Java捆绑在一起的臭名昭著的java.util.Date和java.util.Calendar类。

user3277382的答案所正确指出的那样,请使用Joda-Time或Java 8中的新java.time.*包

Joda-Time 2.3示例代码

DateTimeZone timeZoneNorway = DateTimeZone.forID( "Europe/Oslo" );
DateTime birthDateTime_InNorway = new DateTime( 1985, 1, 1, 3, 2, 1, timeZoneNorway );

DateTimeZone timeZoneNewYork = DateTimeZone.forID( "America/New_York" );
DateTime birthDateTime_InNewYork = birthDateTime_InNorway.toDateTime( timeZoneNewYork ); 

DateTime birthDateTime_UtcGmt = birthDateTime_InNorway.toDateTime( DateTimeZone.UTC );

LocalDate birthDate = new LocalDate( 1985, 1, 1 );

转储到控制台...

System.out.println( "birthDateTime_InNorway: " + birthDateTime_InNorway );
System.out.println( "birthDateTime_InNewYork: " + birthDateTime_InNewYork );
System.out.println( "birthDateTime_UtcGmt: " + birthDateTime_UtcGmt );
System.out.println( "birthDate: " + birthDate );

运行时...

birthDateTime_InNorway: 1985-01-01T03:02:01.000+01:00
birthDateTime_InNewYork: 1984-12-31T21:02:01.000-05:00
birthDateTime_UtcGmt: 1985-01-01T02:02:01.000Z
birthDate: 1985-01-01

java.time

在这种情况下,java.time 的代码与 Joda-Time 的代码几乎完全相同。
我们获取一个时区(ZoneId),并构建一个分配给该时区的日期时间对象(ZonedDateTime)。然后使用 Immutable Objects 模式,基于旧对象的相同瞬间(自epoch以来的纳秒计数)创建新的日期时间对象,但分配到其他时区。最后,我们得到一个 LocalDate,它没有时间和时区,但请注意在确定该日期时应用时区(例如,在 奥斯陆纽约 相比,一天开始得更早)。
ZoneId zoneId_Norway = ZoneId.of( "Europe/Oslo" );
ZonedDateTime zdt_Norway = ZonedDateTime.of( 1985 , 1 , 1 , 3 , 2 , 1 , 0 , zoneId_Norway );

ZoneId zoneId_NewYork = ZonedId.of( "America/New_York" );
ZonedDateTime zdt_NewYork = zdt_Norway.withZoneSameInstant( zoneId_NewYork );

ZonedDateTime zdt_Utc = zdt_Norway.withZoneSameInstant( ZoneOffset.UTC );  // Or, next line is similar.
Instant instant = zdt_Norway.toInstant();  // Instant is always in UTC.

LocalDate localDate_Norway = zdt_Norway.toLocalDate();

关于 java.time

java.time 框架是 Java 8 及更高版本内置的。这些类取代了老旧的 遗留 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

要了解更多,请参阅Oracle教程。并搜索Stack Overflow以获得许多示例和解释。规范是JSR 310

Joda-Time 项目现在处于维护模式,建议迁移到java.time 类。

您可以直接使用与您的数据库兼容的JDBC驱动程序(符合JDBC 4.2或更高版本)来交换java.time对象。不需要字符串,也不需要java.sql.*类。Hibernate 5和JPA 2.2支持java.time

如何获取java.time类?

Table of which java.time library to use with which version of Java or Android


5
感谢您实际提及根本原因并提供所有可用选择的覆盖范围。 - mabi
1
这是正确的答案,应该避免使用Calendar类。ThreeTen是最佳选择。 - ant2009
@Basil Bourque,您提到较新版本的Android捆绑了java.time类的实现。您能否更具体地说明是哪些版本?我正在尝试替换Android应用程序中旧的Date和Calendar代码,但不确定从何处开始。 - AJW
1
@AJW 这些信息已经在答案的最后一部分 "关于java.time" 中提供了,我刚刚更新了它。 - Basil Bourque
非常好,感谢更新。 - AJW

105

具体的 Date 构造函数已经被废弃,应使用 Calendar 代替。 JavaDoc 中说明了哪些构造函数已经被废弃,以及如何使用 Calendar 替换它们。


33
日历需要额外的对象和大约8行代码来完成相同的操作,即从我所知道的内容中创建一个日期对象。当你只需要一个日期而不是一个时区调整的变量时,这很令人困惑并且似乎是不必要的。 - G_V
9
注意,像 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 这样的旧日期时间类现在已经成为 遗留系统,被 Java 8 及更高版本内置的 java.time 类所取代。请参阅 Oracle 的 教程 - Basil Bourque

13

我发现这个问题是 一个更新的问题 的重复,该问题询问获取特定年份、月份和日期的非弃用方式。

到目前为止,这里的答案都建议使用 Calendar 类,这在 Java 8 之前是正确的。但从 Java 8 开始,标准的方法是:

LocalDate localDate = LocalDate.of(1985, 1, 1);

如果你真的需要一个java.util.Date,你可以参考这个问题中的建议。

欲了解更多信息,请查看Java 8的API文档教程


12

构造函数被弃用的一个原因是,年份参数的含义不是您所期望的。Javadoc中写道:

自JDK版本1.1起,被Calendar.set(year + 1900, month, date)替换。

请注意,年份字段是自1900年以来的年数,因此您的示例代码很可能不会执行您所期望的操作。这就是重点。

通常,Date API仅支持现代西方日历,具有独特指定的组件,并且如果您设置字段,则行为不一致。

CalendarGregorianCalendar API优于Date,第三方的Joda-time API通常被认为是最好的。在Java 8中,他们引入了java.time包,现在是推荐的替代方法。


这个解决方案非常好,但它不像 new Date(year, month, day) 那样使用零时间来表示小时-分钟-秒-毫秒。因此我们需要类似以下的代码: Calendar cal = Calendar.getInstance(); cal.clear(); cal.set(year + 1900, month, date); Date dateRepresentation = cal.getTime(); - Joel Richard Koett

9
请注意,Calendar.getTime()是不确定的,因为日期时间部分默认为当前时间。
要复制,请尝试运行以下代码几次:
Calendar c = Calendar.getInstance();
c.set(2010, 2, 7); // NB: 2 means March, not February!
System.err.println(c.getTime());

输出例子:

Sun Mar 07 10:46:21 CET 2010

几分钟后运行完全相同的代码会产生以下结果:
Sun Mar 07 10:57:51 CET 2010

因此,虽然set()可以强制相应的字段具有正确的值,但它会泄漏其他字段的系统时间。(在Sun jdk6和jdk7上进行了测试)

7

6

6

由于日期构造函数已被弃用,您可以尝试使用此代码。

import java.util.Calendar;

  Calendar calendar = Calendar.getInstance();
     calendar.set(Calendar.HOUR_OF_DAY, 6);// for 6 hour
     calendar.set(Calendar.MINUTE, 0);// for 0 min
     calendar.set(Calendar.SECOND, 0);// for 0 sec
     calendar.set(1996,0,26);// for Date [year,month(0 to 11), date]

    Date date = new Date(calendar.getTimeInMillis());// calendar gives long value

    String mConvertedDate = date.toString();// Fri Jan 26 06:00:00 GMT+05:30 1996

2
这些糟糕的类现在已经过时了,多年前被JSR 310中定义的java.time类所取代。在2019年建议使用DateCalendar是不好的建议。 - Basil Bourque
@BasilBourque 感谢您的建议,我期待着尽快更新它。 - Divyanshu Kumar

3
Date构造函数需要以自1900年起的年份格式、从零开始的月份、从一开始的日期,并将小时/分钟/秒/毫秒设置为零。
Date result = new Date(year, month, day);

因为构造函数 Date 已被弃用,我们需要使用日历替代(零起点的年份和月份,一起点的日期),代码如下:

Calendar calendar = Calendar.getInstance();
calendar.clear(); // Sets hours/minutes/seconds/milliseconds to zero
calendar.set(year + 1900, month, day);
Date result = calendar.getTime();

使用Java 1.8(具有从零开始的年份和从一开始的月份和日期):
Date result = Date.from(LocalDate.of(year + 1900, month + 1, day).atStartOfDay(ZoneId.systemDefault()).toInstant());

以下是相等的日期、日历和Java 1.8版本:

int year = 1985; // 1985
int month = 1; // January
int day = 1; // 1st

// Original, 1900-based year, zero-based month, one-based day
Date date1 = new Date(year - 1900, month - 1, day);

// Calendar, zero-based year, zero-based month, one-based day
Calendar calendar = Calendar.getInstance();
calendar.clear(); // Sets hours/minutes/seconds/milliseconds to zero
calendar.set(year, month - 1, day);
Date date2 = calendar.getTime();

// Java-time back to Date, zero-based year, one-based month, one-based day
Date date3 = Date.from(LocalDate.of(year, month, day).atStartOfDay(ZoneId.systemDefault()).toInstant());

SimpleDateFormat format = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss.SSS");

// All 3 print "1985-Jan-01 00:00:00.000"
System.out.println(format.format(date1));
System.out.println(format.format(date2));
System.out.println(format.format(date3));

1
这两个糟糕的类在多年前被 JSR 310 中定义的 java.time 类所取代。 - Basil Bourque
更新答案以显示日期、日历和Java 1.8版本。 - Joel Richard Koett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接