不同对象的哈希码相同
hash code of different objects are the same
我在 java 应用程序(Spring 批处理作业)中遇到一个奇怪的结果,其中一个内部自定义依赖项 - 我们在我公司开发的库 - 已升级。
代码升级后,同一类型的两个新的不同对象显示具有相同的哈希码。
CustomObject oj1 = new CustomObject();
oj1.setId(1234L);
CustomObject oj2 = new CustomObject();
oj2.setId(9999L);
System.out.println(oj1); //Prints CustomObject@1
System.out.println(oj2); //Prints CustomObject@1
System.out.println(oj1.hashCode()); //Prints 1
System.out.println(oj2.hashCode()); //Prints 1
在意识到其中一个具有 HashSet 变量的单元测试仅添加第一个对象而忽略其余对象后,我注意到了这个问题。显然 hashSet 正在做应该做的事情,但对象不应该是相同的,而是具有不同 ID 的新实例。我在应用程序中的单元测试之外测试了同样的东西,但仍然是同样的问题。一旦我恢复到旧的依赖代码,代码就会正常运行,并且上面的打印语句显示不同的数字!
我确信其中一个依赖项导致了这个问题,但我无法确定根本原因。
CustomObject 是通过相同的依赖项间接拉取的,并且没有实现 equals() 和 hashcode(),它只有
private static final long serialVersionUID = 1L;
查看 CustomObject 的源代码揭示了这个实现
public class CustomObject extends BaseModel implements Serializable
并且 BaseModel 定义了 equals 和 hashCode 方法
import org.jvnet.jaxb2_commons.lang.*;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BaseModel")
@XmlSeeAlso({
CustomObject.class
})
public abstract class BaseModel implements Serializable, Equals2, HashCode2
{
private final static long serialVersionUID = 1L;
public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy2 strategy) {
if ((object == null)||(this.getClass()!= object.getClass())) {
return false;
}
if (this == object) {
return true;
}
return true;
}
public boolean equals(Object object) {
final EqualsStrategy2 strategy = JAXBEqualsStrategy.INSTANCE;
return equals(null, null, object, strategy);
}
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
int currentHashCode = 1;
return currentHashCode;
}
public int hashCode() {
final HashCodeStrategy2 strategy = JAXBHashCodeStrategy.INSTANCE;
return this.hashCode(null, strategy);
}
}
提前致谢。
两个不相等的对象具有相同的哈希码是可以的。事实上,这是允许这样做的数学要求。例如,考虑一下字符串:有无限多个不相等的字符串("a"、"aa"、"aaa"...),但只有 2^32 个可能的 int 值。显然必须有不同的字符串共享一个哈希码。
但是 HashSet 知道这一点,所以它使用 equals
的结果以及哈希码。如果只添加了一个对象,那么它们不仅具有相同的散列码——它们是相等的,正如 equals
方法返回的那样。如果没有自定义 class 的代码,我们无法确定这是为什么,更不用说它是否是故意的了。
contract for Object说相等的对象必须有相同的散列码。但反之则不然:具有相同哈希码的对象不必相等。 Javadocs 明确地说:
- It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
除非 class 的文档明确告诉您它是如何计算其哈希码的,否则您可能不应该将其视为已建立的联系,并且您应该预料到它可能会在版本之间发生变化。
显然基础 class 中发生了一些变化,您只需要找到它并修复它,或者在此 [=19] 中可接受地实施 hashCode()
和 equals()
=].
有人在某处实现了 hashCode()
到 return 1,这是愚蠢的。他们最好根本不实施它。而且并不难找。只需查看 CustomObject
的 Javadoc,看看它从哪里继承 hashCode()
。
CustomObject
class 实现(或其祖先之一)是这里的问题。 CustomObject
的作者(或其祖先之一)在不理解其语义及其含义的情况下以错误的方式覆盖了 toString
、hashCode
和 equals
方法。以下是解决问题的选项(不一定按此顺序):
- 您应该将
CustomObject
class 中的问题通知您的依赖库的作者,并实现 toString
、hashCode
和 equals
方法,或者以正确的方式覆盖。但请记住 - 依赖代码作者可以在将来再次将您放回到这个地方。
- 假设
CustomObject
class 不是 final
- 扩展 CustomObject
(请注意 - 它最好命名为 CustomClass
,而不是 CustomObject
) 以正确执行 toString
、hashCode
和 equals
方法。在您的代码中使用此扩展 class 而不是 CustomObject
class。这会给你更好的控制,因为你的依赖代码不会让你再次陷入这个麻烦。
- 使用
AOP
在CustomObject
class中引入toString
、hashCode
和equals
方法的重写和正确实现。这种方法也像上面的选项 2 一样是面向未来的。
我认为您已经意识到问题的答案是什么,但是您在更新中添加的代码清楚地表明了这一点:
您的 CustomClass
扩展了 BaseClass
。
BaseClass
覆盖 Object::hashCode()
您向我们展示的 BaseClass
版本中的覆盖 将始终 return 1. 它正在调用 hashCode(ObjectLocator, HashCodeStrategy2)
方法具有特定的策略,但该方法的实现只是忽略策略参数。
现在很清楚,那个版本的 BaseClass
代码只能 return 1 作为哈希码。但是你说你的代码曾经可以工作,而你只是改变了依赖关系。由此,我们必须得出结论,依赖项已更改,并且新版本的依赖项已损坏。
如果有什么"bizarre"与此有关,那就是有人决定像那样实施(新)BaseClass
,并在没有适当测试的情况下发布它。
实际上,有一种方法可以让您的 CustomClass
正常工作。 BaseClass::hashCode(ObjectLocator, HashCodeStrategy2)
方法是 public 而不是最终方法,因此您可以在 CustomClass
上覆盖它,如下所示:.
@Override
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
return System.identityHashCode(this);
}
事实上,BaseClass
的实施者可能希望您这样做。但我仍然认为 BaseClass
已损坏:
- 编码 class 以便
hashCode
returns 1 作为默认行为是令人讨厌的。
BaseClass
中没有 javadoc 来解释重写方法的必要性。
- 他们(未宣布?)对
BaseClass
行为的更改是 API 重大 更改。你不应该在没有充分理由和警告的情况下做那样的事情。
- 相应
equals
方法的默认行为客观上是错误的。
我在 java 应用程序(Spring 批处理作业)中遇到一个奇怪的结果,其中一个内部自定义依赖项 - 我们在我公司开发的库 - 已升级。 代码升级后,同一类型的两个新的不同对象显示具有相同的哈希码。
CustomObject oj1 = new CustomObject();
oj1.setId(1234L);
CustomObject oj2 = new CustomObject();
oj2.setId(9999L);
System.out.println(oj1); //Prints CustomObject@1
System.out.println(oj2); //Prints CustomObject@1
System.out.println(oj1.hashCode()); //Prints 1
System.out.println(oj2.hashCode()); //Prints 1
在意识到其中一个具有 HashSet 变量的单元测试仅添加第一个对象而忽略其余对象后,我注意到了这个问题。显然 hashSet 正在做应该做的事情,但对象不应该是相同的,而是具有不同 ID 的新实例。我在应用程序中的单元测试之外测试了同样的东西,但仍然是同样的问题。一旦我恢复到旧的依赖代码,代码就会正常运行,并且上面的打印语句显示不同的数字! 我确信其中一个依赖项导致了这个问题,但我无法确定根本原因。 CustomObject 是通过相同的依赖项间接拉取的,并且没有实现 equals() 和 hashcode(),它只有
private static final long serialVersionUID = 1L;
查看 CustomObject 的源代码揭示了这个实现
public class CustomObject extends BaseModel implements Serializable
并且 BaseModel 定义了 equals 和 hashCode 方法
import org.jvnet.jaxb2_commons.lang.*;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;
import java.io.Serializable;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BaseModel")
@XmlSeeAlso({
CustomObject.class
})
public abstract class BaseModel implements Serializable, Equals2, HashCode2
{
private final static long serialVersionUID = 1L;
public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy2 strategy) {
if ((object == null)||(this.getClass()!= object.getClass())) {
return false;
}
if (this == object) {
return true;
}
return true;
}
public boolean equals(Object object) {
final EqualsStrategy2 strategy = JAXBEqualsStrategy.INSTANCE;
return equals(null, null, object, strategy);
}
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
int currentHashCode = 1;
return currentHashCode;
}
public int hashCode() {
final HashCodeStrategy2 strategy = JAXBHashCodeStrategy.INSTANCE;
return this.hashCode(null, strategy);
}
}
提前致谢。
两个不相等的对象具有相同的哈希码是可以的。事实上,这是允许这样做的数学要求。例如,考虑一下字符串:有无限多个不相等的字符串("a"、"aa"、"aaa"...),但只有 2^32 个可能的 int 值。显然必须有不同的字符串共享一个哈希码。
但是 HashSet 知道这一点,所以它使用 equals
的结果以及哈希码。如果只添加了一个对象,那么它们不仅具有相同的散列码——它们是相等的,正如 equals
方法返回的那样。如果没有自定义 class 的代码,我们无法确定这是为什么,更不用说它是否是故意的了。
contract for Object说相等的对象必须有相同的散列码。但反之则不然:具有相同哈希码的对象不必相等。 Javadocs 明确地说:
- It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
除非 class 的文档明确告诉您它是如何计算其哈希码的,否则您可能不应该将其视为已建立的联系,并且您应该预料到它可能会在版本之间发生变化。
显然基础 class 中发生了一些变化,您只需要找到它并修复它,或者在此 [=19] 中可接受地实施 hashCode()
和 equals()
=].
有人在某处实现了 hashCode()
到 return 1,这是愚蠢的。他们最好根本不实施它。而且并不难找。只需查看 CustomObject
的 Javadoc,看看它从哪里继承 hashCode()
。
CustomObject
class 实现(或其祖先之一)是这里的问题。 CustomObject
的作者(或其祖先之一)在不理解其语义及其含义的情况下以错误的方式覆盖了 toString
、hashCode
和 equals
方法。以下是解决问题的选项(不一定按此顺序):
- 您应该将
CustomObject
class 中的问题通知您的依赖库的作者,并实现toString
、hashCode
和equals
方法,或者以正确的方式覆盖。但请记住 - 依赖代码作者可以在将来再次将您放回到这个地方。 - 假设
CustomObject
class 不是final
- 扩展CustomObject
(请注意 - 它最好命名为CustomClass
,而不是CustomObject
) 以正确执行toString
、hashCode
和equals
方法。在您的代码中使用此扩展 class 而不是CustomObject
class。这会给你更好的控制,因为你的依赖代码不会让你再次陷入这个麻烦。 - 使用
AOP
在CustomObject
class中引入toString
、hashCode
和equals
方法的重写和正确实现。这种方法也像上面的选项 2 一样是面向未来的。
我认为您已经意识到问题的答案是什么,但是您在更新中添加的代码清楚地表明了这一点:
您的
CustomClass
扩展了BaseClass
。BaseClass
覆盖Object::hashCode()
您向我们展示的
BaseClass
版本中的覆盖 将始终 return 1. 它正在调用hashCode(ObjectLocator, HashCodeStrategy2)
方法具有特定的策略,但该方法的实现只是忽略策略参数。
现在很清楚,那个版本的 BaseClass
代码只能 return 1 作为哈希码。但是你说你的代码曾经可以工作,而你只是改变了依赖关系。由此,我们必须得出结论,依赖项已更改,并且新版本的依赖项已损坏。
如果有什么"bizarre"与此有关,那就是有人决定像那样实施(新)BaseClass
,并在没有适当测试的情况下发布它。
实际上,有一种方法可以让您的 CustomClass
正常工作。 BaseClass::hashCode(ObjectLocator, HashCodeStrategy2)
方法是 public 而不是最终方法,因此您可以在 CustomClass
上覆盖它,如下所示:.
@Override
public int hashCode(ObjectLocator locator, HashCodeStrategy2 strategy) {
return System.identityHashCode(this);
}
事实上,BaseClass
的实施者可能希望您这样做。但我仍然认为 BaseClass
已损坏:
- 编码 class 以便
hashCode
returns 1 作为默认行为是令人讨厌的。 BaseClass
中没有 javadoc 来解释重写方法的必要性。- 他们(未宣布?)对
BaseClass
行为的更改是 API 重大 更改。你不应该在没有充分理由和警告的情况下做那样的事情。 - 相应
equals
方法的默认行为客观上是错误的。