java.util.Date equals()似乎没有按预期工作

时间:2014-12-26 20:27:46

标签: java date

问题

我有一个Map<Date, Foo>,以及一个具有effectiveDate属性的数据库对象列表,我想检查一下我的地图中的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)不应该equalscompareTo同意吗? (我假设java.util.Date的实施至少应该尝试遵循java.lang.Comparable的建议。

2)The Date#equals doc says this

  

因此,当且仅当getTime方法为两者返回相同的long值时,两个Date对象才相等。

...看起来getTime方法为这两个日期返回相同的long值,但equal返回false。任何想法为什么会这样?我已经搜索过高低,但我还没有发现任何人描述同样的问题。

P.S。我使用java.util.Date卡住了。请不要只推荐JodaTime。

P.P.S。我意识到我可以改变这段代码的结构,并可能让它运行起来。但这应该有效,我不想只是解决它,除非它是一个已知的问题或其他什么。它似乎错误

3 个答案:

答案 0 :(得分:8)

正如Mureinik暗示的那样,Sotirios Delimanolis更具体地指出,这里的问题是java.util.Date的实施。

java.util.Datejava.sql包中扩展了3个类,所有这些类似乎都做了类似的事情,并且它们在java中的区别并不清楚(看起来它们存在的原因很简单使java类更准确地与SQL数据类型对齐) - 有关它们差异的更多信息,请查看this very detailed answer

现在,在一个看似严重的设计缺陷中,有人决定制作equals() asymmetric with java.sql.Timestamp - 也就是说,即使timestamp.equals(date)返回true,date.equals(timestamp)也可能返回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 uses parameter.equals(key) in it's implementation of containsKey()(而不是key.equals(parameter))以来,这一奇怪的结果出现在给定的情况中。

那么,如何解决这个问题呢?

1)在地图中使用Long键,而不是Date(正如Mureinik所说) - 因为java.util.Datejava.util.Timestamp从{{1}返回相同的值不管你正在使用哪种实现都不重要,密钥也是一样的。这种方式看似最简单。

2)在地图中使用之前标准化日期对象。这种方式需要更多的工作,但对我来说似乎更令人满意,因为它更清楚地图是什么 - 一堆getTime()每个都存储在一个时刻。这是我最终使用的方式,使用以下方法:

Foo

这需要一个额外的方法调用(并且有点可笑),但对我而言,涉及public Date getStandardizedDate(Date date) { return new Date(date.getTime()); } 的代码的可读性增加是值得的。

答案 1 :(得分:3)

第1部分:&#34;不应该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()))

答案 2 :(得分:2)

从数据库返回的Date对象可能是java.sql.Timestamp,它不能等于java.util.Date个对象。我只需从getTime()返回,并将其用作HashMap中的密钥。