java.util.Date equals()似乎没有按预期工作
问题
我有一个Map
,以及一个带有effectiveDate
属性的数据库中的对象列表,我想查看我的map中的Date
键是否等于数据库中的任何effectiveDate
– 如果所以,用Foo
做事。
代码看起来像这样:
for (Bar bar : databaseBars) { Foo foo = new Foo(); if (dateMap.containsKey(bar.getEffectiveDate()) { foo = dateMap.get(bar.getEffectiveDate()); } // do stuff with foo and bar }
但是, dateMap.containsKey
调用总是返回false,即使我确定它有时也存在。
调查
作为一个完整性检查,我打印出日期的长值,以及equals()
调用和compareTo()
调用的结果:
for (Date keyDate : dateMap.keySet()) { if (keyDate == null) { continue; // make things simpler for now } Date effDate = bar.getEffectiveDate(); String template = "keyDate: %d; effDate: %d; equals: %b; compareTo: %d\n"; System.out.printf(template, keyDate.getTime(), effDate.getTime(), effDate.equals(keyDate), effDate.compareTo(keyDate)); }
结果:
keyDate: 1388534400000; effDate: 1388534400000; equals: false; compareTo: 0 keyDate: 1420070400000; effDate: 1388534400000; equals: false; compareTo: -1 keyDate: 1388534400000; effDate: 1420070400000; equals: false; compareTo: 1 keyDate: 1420070400000; effDate: 1420070400000; equals: false; compareTo: 0 keyDate: 1388534400000; effDate: 1388534400000; equals: false; compareTo: 0 keyDate: 1420070400000; effDate: 1388534400000; equals: false; compareTo: -1 keyDate: 1388534400000; effDate: 1420070400000; equals: false; compareTo: 1 keyDate: 1420070400000; effDate: 1420070400000; equals: false; compareTo: 0 keyDate: 1388534400000; effDate: 1388534400000; equals: false; compareTo: 0 keyDate: 1420070400000; effDate: 1388534400000; equals: false; compareTo: -1 keyDate: 1388534400000; effDate: 1420070400000; equals: false; compareTo: 1 keyDate: 1420070400000; effDate: 1420070400000; equals: false; compareTo: 0
题
1)不应该equals
和比较同意吗? (我假设java.util.Date
的实现至少应该尝试遵循java.lang.Comparable
的建议)。
2) Date#equals
doc说 :
因此,当且仅当getTime方法为两者返回相同的long值时,两个Date对象才相等。
…看起来getTime
方法为这两个日期返回相同的long值,但是equal
返回false。 任何想法为什么会这样? 我搜索过高低,但我没有发现任何人描述同样的问题。
PS我坚持使用java.util.Date
。 请不要只推荐JodaTime。
PPS我意识到我可以改变这段代码的结构,并可能让它运行起来。 但这应该有效,我不想只是解决它,除非它是一个已知的问题或什么的。 这似乎不对 。
正如Mureinik暗示和Sotirios Delimanolis更具体地指出的那样,问题在于java.util.Date
的实现。
java.util.Date
在java.sql
包java.util.Date
扩展了3个类,所有这些类似乎做了类似的事情,并且它们在java中的区别一点也不清楚(似乎它们存在的原因只是使java类成为更准确地对齐SQL数据类型) – 有关它们差异的更多信息,请查看这个非常详细的答案 。
现在,在看似严重的设计缺陷中,有人决定使用java.sql.Timestamp
使equals()
不对称 – 也就是说,即使date.equals(timestamp)
返回true, timestamp.equals(date)
也可能返回false。 好想法。
我写了几行来看看哪些java.sql
类演示了这个荒谬的属性 – 显然它只是Timestamp
。 这段代码:
java.util.Date utilDate = new java.util.Date(); java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime()); System.out.println("sqlDate equals utilDate:\t" + sqlDate.equals(utilDate)); System.out.println("utilDate equals sqlDate:\t" + utilDate.equals(sqlDate)); java.sql.Time time = new java.sql.Time(utilDate.getTime()); System.out.println("time equals utilDate:\t\t" + time.equals(utilDate)); System.out.println("utilDate equals time:\t\t" + utilDate.equals(time)); java.sql.Timestamp timestamp = new java.sql.Timestamp(utilDate.getTime()); System.out.println("timestamp equals utilDate:\t" + timestamp.equals(utilDate)); System.out.println("utilDate equals timestamp:\t" + utilDate.equals(timestamp));
收益率:
sqlDate equals utilDate: true utilDate equals sqlDate: true time equals utilDate: true utilDate equals time: true timestamp equals utilDate: false utilDate equals timestamp: true
由于java.util.HashMap
在其执行containsKey()
(而不是key.equals(parameter)
)时使用了parameter.equals(key)
,因此在给定的情况下会出现这个奇怪的结果。
那么,如何解决这个问题呢?
1)在地图中使用Long
键而不是Date
(如Mureinik所说) – 因为java.util.Date
和java.util.Timestamp
从getTime()
返回相同的值,所以你应该使用哪个实现无关紧要使用,关键将是相同的。 这种方式看似最简单。
2)在地图中使用之前标准化日期对象。 这种方式需要更多的工作,但对我来说似乎更令人满意,因为它更清楚的是地图是什么 – 一堆Foo
每个存储在一个时刻。 这是我最终使用的方式,使用以下方法:
public Date getStandardizedDate(Date date) { return new Date(date.getTime()); }
它需要一个额外的方法调用(在那种情况下是一种荒谬的方法),但对我来说,涉及Map
的代码的可读性增加是值得的。
第1部分:“不应该equals
compareTo
?*
没有; compareTo应该同意equals,但反过来是无关紧要的。
compareTo是关于排序顺序。 等于是平等。 考虑赛车,可以在实践中按最快单圈时间排序以确定起始位置。 平均单圈时间并不意味着它们是同一辆车。
第2部分:平等日期。
数据库调用将返回一个java.sql.Date,虽然它可以赋值给java.util.Dare,因为它扩展了它,不会相等,因为类是不同的。
解决方法可能是:
java.util.Date test; java.sql.Date date; if (date.equals(new java.sql.Date(test.getTime()))
从数据库返回的Date
对象可能是java.sql.Timestamp
,它不能等于java.util.Date
对象。 我只需从getTime()
返回long,并将其用作HashMap
的键。