文档章节

java.util.Date 毫秒去哪了?

cwalet
 cwalet
发布于 2015/05/29 17:06
字数 874
阅读 163
收藏 0

(一). 问题代码示例

import java.sql.Timestamp; 
import java.util.Date;  

public class Test {     
    
    public static void main(String[] args) {
        Timestamp d1 = Timestamp.valueOf("2015-04-30 06:10:11.027");
        Timestamp d2 = Timestamp.valueOf("2015-04-30 06:10:11.030");
        Date d3 = new Date(1430345411027L);
        Date d4 = new Date(1430345411030L);
        Date d5 = d1;
        Date d6 = d2;
        System.out.println(d1.before(d2));
        System.out.println(d3.before(d4));
        System.out.println(d5.before(d6));
    }
}
代码本身没有任何语法错误,费解的是它执行的结果:
true
true
false
输出的前两行很正常,显然d1和d3分别早于d2和d4,但d5却是晚于d6!?


(二). 复习 Java 基础知识

1. Java向上转型与向下转型

Java中子类向父类转型(向上转型)时,以下三点规则很重要:

  • 无论是方法还是变量(default、protected)都是调用的子类的;
  • 如果子类没有或已重载某个方法时,则调用的是父类的方法;
  • 如果子类有而父类没有则编译报错,总之是向父类看齐。

2. 关于构造方法:

  • 子类的构造方法执行过程中会先自动调用父类的无参构造方法;
  • 如果显式调用 super([args...]),则不会再自动调用父类的无参构造方法;
  • super([args...]) 必须写在子类构造函数的第一行,以在语法上申明一种从属关系;
  • 如果父类的无参构造方法为 private,则子类必须显式调用父类的其他公共有参构造方法。


(三). 分析执行过程

Date 类中的方法是:

public boolean before(Date when) {
    return getMillisOf(this) < getMillisOf(when);
}
Timestamp 重载了该方法:
public boolean before(Timestamp ts) {
    return compareTo(ts) < 0;
}

因此代码中实际调用的是Date 类的 before方法,而非子类(Timestamp)的 before方法。

Date类中的 before方法实际上是调用的 getMillisOf方法来判断,并且Timestamp类中并没有重写该方法。

既然如此,那么getMillisOf 方法也是使用的父类Date 中的实现了:

static final long getMillisOf(Date date) {
    if (date.cdate == null || date.cdate.isNormalized()) {
        return date.fastTime;
    }
    BaseCalendar.Date d = (BaseCalendar.Date) date.cdate.clone();
    return gcal.getTime(d);
}

Date 中的有个内部变量 cdate,Timestamp中同样没有,这个变量在何时初始化呢?

显然d5、d6的Date类实际上在d1、d2声明时就已初始化好(调用父类构造函数):

public static Timestamp valueOf(String s) {
    ...
    return new Timestamp(year - 1900, month - 1, day, hour, minute, second, a_nanos);
}

在来看看Timestamp 类的这个带参构造方法:

public Timestamp(int year, int month, int date, int hour, int minute, int second, int nano) {
    super(year, month, date, hour, minute, second);
    if (nano > 999999999 || nano < 0) {
        throw new IllegalArgumentException("nanos > 999999999 or < 0");
    }
    nanos = nano;
}

其中显示调用了父类Date 类的带参构造方法,并且没有传入nanos(毫秒),而是将其保存在私有变量中,

因此,Timestamp向父类Date转型时丢失了毫秒,导致后面调用Date类的before方法时出现错误的结果。


(四). 后记

几年前写过一篇与Timestamp类相关的文章:java.sql.Date 时分秒去哪了?

没想到几年后又遇到类似的问题,虽然没有造成携程宕机事件的严重后果,但也足以警醒世猿。

解决方法是:

在任何场合关于时间类型的比较都比较其数字时间戳,使用 getTime() 方法,防止此类隐藏问题。

至于Date类的实现中为何要加入一个BaseCalendar.Date 类型的cdate,以及fastTime 就不得而知,还望相告;

另外,已有达人做出更详细的解释,请参考:JDK BUG吗? 混乱的日期API

© 著作权归作者所有

cwalet
粉丝 44
博文 111
码字总数 87663
作品 0
其他
私信 提问
util.Date、sql.Date、sql.Time、sql.Timestamp区别和联系

在Web开发中,避免不了对日期的操作,常用的关于时间的类有这么几个: java.util.Date、java.sql.Date、java.sql.Time、java.sql.Timestamp,这几个类在JDK的定义如下所示: java.lang.Obje...

文文1
2016/02/26
400
0
Java 时间 Date类型,Long类型,String类型

Java 日期时间 Date类型,long类型,String类型表现形式的转换 1、java.util.Date类型转换成long类型 java.util.Date dt = new Date(); System.out.println(dt.toString()); //java.util.Date的......

叶大文
2014/04/03
410
0
Date对象存入mysql数据库

java.sql.Date,java.sql.Time和java.sql.Timestamp三个都是java.util.Date的子类(包装类)。 java.sql.Date是java.util.Date的子类,是一个包装了毫秒值的瘦包装器,允许 JDBC 将毫秒值标识...

颜建海
2014/04/04
8.6K
0
Java中Date及Timestamp时间相关内容【转】

JavaSQLDAO java.util.date java.sql.date java.sql.timestamp 整理一: 这里的一片文章,我个人认为讲解的很详细,有对 java.sql.Date的使用还有困惑的请看。 java.sql.Date 只存储日期数据...

houyiwujing
2012/03/07
1K
0
java.sql.date与java.util.date区别以及数据库中插入带时分秒的时间

java.sql.Date,java.sql.Time和java.sql.Timestamp三个都是java.util.Date的子类(包装类)。 java.sql.Date是java.util.Date的子类,是一个包装了毫秒 值的瘦包装器,允许 JDBC 将毫秒值标识...

村长大神
2015/02/02
176
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud 笔记之Spring cloud config client

观察者模式它的数据的变化是被动的。 观察者模式在java中的实现: package com.hxq.springcloud.springcloudconfigclient;import org.springframework.context.ApplicationListener;i...

xiaoxiao_go
昨天
4
0
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
昨天
4
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
昨天
7
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
昨天
7
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部