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

2022-09-03 01:56:08

问题

我有一个,以及数据库中带有属性的对象列表,我想检查我的映射中的键是否等于数据库中的任何s - 如果是这样,请使用.Map<Date, Foo>effectiveDateDateeffectiveDateFoo

代码如下所示:

for (Bar bar : databaseBars) {
  Foo foo = new Foo();
  if (dateMap.containsKey(bar.getEffectiveDate()) {
    foo = dateMap.get(bar.getEffectiveDate());
  }
  // do stuff with foo and bar
}

但是,该调用始终返回 false,即使我确信它有时存在。dateMap.containsKey

调查

作为健全性检查,我打印出了日期的长值,以及呼叫和调用的结果: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)不应该同意吗?(我假设的实现至少应该尽量遵循的建议)。equalscompareTojava.util.Datejava.lang.Comparable

2) Date#equals doc 是这样说的

因此,当且仅当 getTime 方法为两个对象返回相同的长整型值时,两个 Date 对象是相等的。

...看起来该方法为这两个日期返回相同的长整型值,但返回 false。任何想法为什么会发生这种情况?我搜索了高和低,但我没有找到任何人描述同样的问题。getTimeequal

附言:我被困在使用.请不要只是推荐JodaTime。java.util.Date

附言我意识到我可以改变这段代码的结构,并可能让它工作。但这应该有效,我不想只是解决它,除非它是一个已知的问题或其他东西。这似乎是错误的


答案 1

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

java.util.Date由包中的3个类扩展,所有这些类似乎都做了类似的事情,并且它们在java中的区别根本不清楚(似乎它们存在的原因只是为了使java类更准确地与SQL数据类型对齐) - 有关它们差异的更多信息,请查看这个非常详细的答案java.sql

现在,在似乎是一个严重的设计缺陷中,有人决定java.sql.Timestamp使equals()不对称 - 也就是说,即使返回true,也可能返回false。好主意。timestamp.equals(date)date.equals(timestamp)

我写了几行,看看哪些类演示了这个荒谬的属性 - 显然它只是。此代码:java.sqlTimestamp

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.HashMapentainsKey(而不是)的实现中使用了参数.equals(key),因此在给定的情况下会出现这个奇怪的结果。key.equals(parameter)

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

1)在地图中使用键而不是(如Muranik所指出的) - 因为并且从返回相同的值,因此无论您使用哪种实现,键都是相同的。这种方式似乎是最简单的。LongDatejava.util.Datejava.util.TimestampgetTime()

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

public Date getStandardizedDate(Date date) {
  return new Date(date.getTime());
}

它需要一个额外的方法调用(这有点荒谬),但对我来说,涉及的代码的可读性提高是值得的。Map<Date, Foo>


答案 2

第1部分:“不应该同意?equalscompareTo

否;compareTo应该同意相等,但反之则无关紧要。

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()))