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 |
1. Java向上转型与向下转型:
Java中子类向父类转型(向上转型)时,以下三点规则很重要:
2. 关于构造方法:
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