美文网首页Java 杂谈
java.util.Date的黑历史 【译文】

java.util.Date的黑历史 【译文】

作者: Lyrieek | 来源:发表于2019-01-07 00:26 被阅读36次

译者前言:翻译的是一篇两年前的文章,大神写的,挺有意思,起因是Stack Overflow上有很多关于java.util.Date的问题,当然其中不少是小白问的,不过也间接的说明这个类是比较坑的,不然不会出这么多问题,包括java.sql.Date的出现,还有它类上的注释,以及下文提到的20年前java.util.Date的大部分方法就被废弃了,都佐证了这一点,这个类的设计是失败的。

原文是英国大神Jon Skeet写的,十四年前就写开始技术博客了,OOP鼻祖,他有多神呢?据说“他的代码如果不能运行,那一定是编译器的错”。我和他一样喜欢C#,不过十四年前我中文还不会写,更别说C#了,时光飞逝啊,这喜欢C#的最后都写上了Java,不提微软的破事了,都是泪啊,下面正文

很少有像java.util.Date的java类在Stack Overflow上引起如此多的问题。这有四个原因:

  • 日期和时间底层实现相当复杂,同时会遇上各种情况。虽然它是可管理的,但需要花时间去理解它。
  • java.util.Date在很多方面都很糟糕(详见下文)。
  • 一般来说,开发人员对它了解很少。
  • 它被很多类库严重滥用,进一步加剧了混乱。

简而言之

  • 如果可能的话,你应该避免它。尽可能使用java.time.*,如果您还没有使用Java 8 及以上版本,则使用ThreeTen-Backport(基本上是旧版本的java.time)Joda Time
  • 如果你非要用它,就避免使用已废弃的方法。他们中的大多数已被弃用近20年,并且有充分的理由。
  • 如果您真的觉得必须使用已弃用的方法,请确保真正理解这些方法。

一个Date实例表示即时的时间,而不是日期。这意味着Date存在以下问题:

  • 没有时区。
  • 没有格式。
  • 没有日历系统。

注:文中提到的即时指的是instant,后面会提到

现在一条一条的喷java.util.Date

java.util.Date (下文简称Date)是一个糟糕的类型,这解释了为什么它在Java 1.1中被废弃了很多内容(但不幸的是,它仍被使用)。

设计缺陷包括:

  • 它的名字具有误导性:它不代表a Date,它代表的是“即时”。所以它的类名其实应该写成Instant,因为它和java.time是等价物。
  • 它不是常量:鼓励继承类更改时间,例如java.sql.Date(意思是代表一个日期,也因为有相同的简称而混淆)
  • 它是可变的:日期/时间类型是自然值,它们由不可变类型有用地建模。Date可变的事实(例如通过该setTime方法)意味着勤奋的开发人员最终会在整个地方创建防御性副本。
  • 它隐含地在许多地方使用系统本地时区 - 包括toString()- 这使许多开发人员感到困惑。
  • 它的月份编号从0开始,是从C复制过来的。这导致了很多很多的错误。
  • 它的年份编号是1900年,也是从C复制的。当Java出来的时候我们有一个想法,这对可读性有害吗?
  • 其方法名称不明确:getDate()返回日期,并getDay()返回星期几。给那些更具描述性的名字有多难?
  • 关于它是否支持闰秒是不明确的:“第二个由0到61之间的整数表示; 值60和61仅在闰秒发生,甚至仅在实际正确跟踪闰秒的Java实现中发生。“我强烈怀疑大多数开发人员(包括我自己)做出了大量假设,其范围getSeconds()实际上在0范围内-59包容。
  • 设置的时间不必在合法的范围内; 例如,日期可以指定为1月32日,然后被解释为2月1日。这不是在搞笑吗?

我可以找到更多的问题,但他们会变得更挑剔。这是一个很好的清单。从积极的一面:

  • 它明确地表示单个值:即时,没有关联的日历系统,时区或文本格式,精确到毫秒。

不幸的是,开发人员对这个“好方面”的了解甚少。我们细说......

什么是“即时”?

注意:我相对性的忽略了本文其余部分和闰秒。它们对某些人来说非常重要,但对于大多数读者来说,会搞得更多人看不懂。

当我谈到“即时”时,我正在谈论可用于识别事情何时发生的那种概念。(可能是将来,但最容易考虑过去的情况。)它与时区和日历系统无关,因此多人使用他们的“本地”时间表示可以用不同的方式来讨论它。

让我们使用一个非常具体的例子,说明在某个地方不会使用我们熟悉的任何时区:Neil Armstrong在月球上行走。月球漫步在特定时刻开始,如果来自世界各地的多个人同时观看,他们都会(几乎)同时说“我现在可以看到它”。

如果你在休斯敦的任务控制中观察,你想的那个即时是“1969年7月20日,CDT时间9:56:20”。如果你在伦敦观看,你想的那个即时是“1969年7月21日,BST凌晨3:26:20”。如果你在沙特的利雅得观看,你想的那个即时是“Jumādá7th1389,5:56:20 am(+03)”(使用Umm al-Qura日历)。即使不同的观察者会在他们的时钟上看到不同的时间 - 甚至不同的年份 - 他们仍然会考虑同一时刻。他们只是应用不同的时区和日历系统,从即时转变为更加以人为中心的概念。

那么计算机如何表示即时呢?它们通常在特定时刻之前或之后存储一定量的时间,该时刻实际上是原点。许多系统都使用Unix时代,这是UTC中格里高利历中表示的即时,即1970年1月1日午夜。这并不意味着时代本身就是“在”UTC中 - 同样可以定义Unix时代作为“1969年12月31日晚上7点在纽约的时刻”。

Date班采用“毫秒自Unix纪元” -这是返回的值getTime(),以及在由设置Date(long)构造函数或setTime()方法。由于月球行走发生在Unix时代之前,因此价值为负:它实际上是-14159020000。

为了演示如何Date与系统时区进行交互,让我们展示之前提到的三个时区 - 休斯顿(美国/芝加哥),伦敦(欧洲/伦敦)和利雅得(亚洲/利雅得)。当我们从其划分时间值构建日期时,系统时区是什么并不重要 - 这完全不依赖于当地时区。但是如果我们使用Date.toString(),它会转换为当前的默认时区以显示结果。更改默认时区根本不会更改该Date的值。对象的内部状态完全相同。它仍然代表同一时刻,但方法一样toString()getMonth()getDate()会受到影响。以下示例代码显示:

import java.util.Date;
import java.util.TimeZone;

public class Test {

    public static void main(String[] args) {
        // The default time zone makes no difference when constructing
        // a Date from a milliseconds-since-Unix-epoch value
        Date date = new Date(-14159020000L);

        // Display the instant in three different time zones
        TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
        System.out.println(date);

        TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
        System.out.println(date);

        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Riyadh"));
        System.out.println(date);

        // Prove that the instant hasn't changed...
        System.out.println(date.getTime());
    }
}

输出如下:

Sun Jul 20 21:56:20 CDT 1969
Mon Jul 21 03:56:20 GMT 1969
Mon Jul 21 05:56:20 AST 1969
-14159020000

输出中的“GMT”和“AST”缩写是非常不幸的 - java.util.TimeZone在所有情况下都没有1970年以前的正确名称。虽然本次是正确的。

常见问题

如何将Date转换为其他时区?

转不了 - 因为Date没有时区。这是一瞬间的时间。不要输出愚蠢的toString()。那是在默认时区显示的时间。它没有价值。
如果以Date作为输入,则已经发生从“本地时间和时区”到即时的任何转换。(希望它能正确完成...)

如果你打算编写一个下面这样的方法,你只能掉坑里:

// 不管怎么写都是错的
Date convertTimeZone(Date input, TimeZone fromZone, TimeZone toZone)

如何将Date转换为其他格式?

转不了,因为Date没有格式。不要输出愚蠢的toString()。它总是使用相同的格式,如文档所述。 要Date以特定方式格式化,请使用合适的DateFormat(一般用SimpleDateFormat) - 记住将时区设置成你要用的时区。

原文:https://codeblog.jonskeet.uk/2017/04/23/all-about-java-util-date/

相关文章

  • java.util.Date的黑历史 【译文】

    译者前言:翻译的是一篇两年前的文章,大神写的,挺有意思,起因是Stack Overflow上有很多关于java.u...

  • 《黑兽》译文

    闻李太公敬一言:“某公在沈阳,宴集山颠,俯瞰山下,有虎衔物来,以爪穴地,瘗之而去。使人探所瘗得死鹿,乃取鹿而掩其穴...

  • 黑历史

    靓影舞于幕台上 又曾漫姿锦屏旁 轩榭楼台歌一曲 繁烟落景回初凉

  • 黑历史

    今天四点半就被儿子的机器人闹钟给闹醒了,离我自己定的时间提前了半小时,只好起床了,边听音频边进行桌面的清理清洁。 ...

  • 黑历史

    【文梗挑战】以“我张开了手臂,抱住了面前的人”为结尾写一篇虐文 是去年暑假心血来潮的黑历史。 和她的初见是...

  • 黑历史

    小满后突然水银柱突然飙升。问:温度计在哪儿?答:莫向外求。——没错,比吃了秤砣还要沉甸甸的,然而丝毫没有丰收的喜悦...

  • 黑历史

  • 黑历史

  • 黑历史

  • 黑历史④

网友评论

    本文标题:java.util.Date的黑历史 【译文】

    本文链接:https://www.haomeiwen.com/subject/yhaerqtx.html