每个Java对象都有hashCode()和equals()方法。许多类忽略(Override)这些方法的缺省实施,以在对象实例之间提供更深层次的语义可比性。在Java理念和实践这一部分,Java开发人员BrianGoetz向您介绍在创建Java类以有效和准确定义hashCode()和equals()时应遵循的规则和指南。您可以在讨论论坛与作者和其它读者一同探讨您对本文的看法。(您还可以点击本文顶部或底部的讨论进入论坛。)
虽然Java语言不直接支持关联数组--可以使用任何对象作为一个索引的数组--但在根Object类中使用hashCode()方法明确表示期望广泛使用HashMap(及其前辈Hashtable)。理想情况下基于散列的容器提供有效插入和有效检索;直接在对象模式中支持散列可以促进基于散列的容器的开发和使用。
定义对象的相等性
Object类有两种方法来推断对象的标识:equals()和hashCode()。一般来说,如果您忽略了其中一种,您必须同时忽略这两种,因为两者之间有必须维持的至关重要的关系。特殊情况是根据equals()方法,如果两个对象是相等的,它们必须有相同的hashCode()值(尽管这通常不是真的)。
特定类的equals()的语义在Implementer的左侧定义;定义对特定类来说equals()意味着什么是其设计工作的一部分。Object提供的缺省实施简单引用下面等式:
publicbooleanequals(Objectobj){return(this==obj);}
在这种缺省实施情况下,只有它们引用真正同一个对象时这两个引用才是相等的。同样,Object提供的hashCode()的缺省实施通过将对象的内存地址对映于一个整数值来生成。由于在某些架构上,地址空间大于int值的范围,两个不同的对象有相同的hashCode()是可能的。如果您忽略了hashCode(),您仍旧可以使用System.identityHashCode()方法来接入这类缺省值。
忽略equals()--简单实例
缺省情况下,equals()和hashCode()基于标识的实施是合理的,但对于某些类来说,它们希望放宽等式的定义。例如,Integer类定义equals()与下面类似:
publicbooleanequals(Objectobj){
return(objinstanceofInteger
&&intValue()==((Integer)obj).intValue());
}
在这个定义中,只有在包含相同的整数值的情况下这两个Integer对象是相等的。结合将不可修改的Integer,这使得使用Integer作为HashMap中的关键字是切实可行的。这种基于值的Equal方法可以由Java类库中的所有原始封装类使用,如Integer、Float、Character和Boolean以及String(如果两个String对象包含相同顺序的字符,那它们是相等的)。由于这些类都是不可修改的并且可以实施hashCode()和equals(),它们都可以做为很好的散列关键字。
为什么忽略equals()和hashCode()?
如果Integer不忽略equals()和hashCode()情况又将如何?如果我们从未在HashMap或其它基于散列的集合中使用Integer作为关键字的话,什么也不会发生。但是,如果我们在HashMap中使用这类Integer对象作为关键字,我们将不能够可靠地检索相关的值,除非我们在get()调用中使用与put()调用中极其类似的Integer实例。这要求确保在我们的整个程序中,只能使用对应于特定整数值的Integer对象的一个实例。不用说,这种方法极不方便而且错误频频。
Object的interfacecontract要求如果根据equals()两个对象是相等的,那么它们必须有相同的hashCode()值。当其识别能力整个包含在equals()中时,为什么我们的根对象类需要hashCode()?hashCode()方法纯粹用于提高效率。Java平台设计人员预计到了典型Java应用程序中基于散列的集合类(CollectionClass)的重要性--如Hashtable、HashMap和HashSet,并且使用equals()与许多对象进行比较在计算方面非常昂贵。使所有Java对象都能够支持hashCode()并结合使用基于散列的集合,可以实现有效的存储和检索。
实施equals()和hashCode()的需求
实施equals()和hashCode()有一些限制,Object文件中列举出了这些限制。特别是equals()方法必须显示以下属性:
Symmetry:两个引用,a和b,a.equals(b)ifandonlyifb.equals(a)
Reflexivity:所有非空引用,a.equals(a)
Transitivity:Ifa.equals(b)andb.equals(c),thena.equals(c)
ConsistencywithhashCode():两个相等的对象必须有相同的hashCode()值
Object的规范中并没有明确要求equals()和hashCode()必须一致--它们的结果在随后的调用中将是相同的,假设“不改变对象相等性比较中使用的任何信息。”这听起来象“计算的结果将不改变,除非实际情况如此。”这一模糊声明通常解释为相等性和散列值计算应是对象的可确定性功能,而不是其它。
对象相等性意味着什么?
人们很容易满足Object类规范对equals()和hashCode()的要求。决定是否和如何忽略equals()除了判断以外,还要求其它。在简单的不可修值类中,如Integer(事实上是几乎所有不可修改的类),选择相当明显--相等性应基于基本对象状态的相等性。在Integer情况下,对象的唯一状态是基本的整数值。
对于可修改对象来说,答案并不总是如此清楚。equals()和hashCode()是否应基于对象的标识(象缺省实施)或对象的状态(象Integer和String)?没有简单的答案--它取决于类的计划使用。对于象List和Map这样的容器来说,人们对此争论不已。Java类库中的大多数类,包括容器类,错误出现在根据对象状态来提供equals()和hashCode()实施。
如果对象的hashCode()值可以基于其状态进行更改,那么当使用这类对象作为基于散列的集合中的关键字时我们必须注意,确保当它们用于作为散列关键字时,我们并不允许更改它们的状态。所有基于散列的集合假设,当对象的散列值用于作为集合中的关键字时它不会改变。如果当关键字在集合中时它的散列代码被更改,那么将产生一些不可预测和容易混淆的结果。实践过程中这通常不是问题--我们并不经常使用象List这样的可修改对象做为HashMap中的关键字。
一个简单的可修改类的例子是Point,它根据状态来定义equals()和hashCode()。如果两个Point对象引用相同的(x,y)座标,Point的散列值来源于x和y座标值的IEEE754-bit表示,那么它们是相等的。
对于比较复杂的类来说,equals()和hashCode()的行为可能甚至受到superclass或interface的影响。例如,List接口要求如果并且只有另一个对象是List,而且它们有相同顺序的相同的Elements(由Element上的Object.equals()定义),List对象等于另一个对象。hashCode()的需求更特殊--list的hashCode()值必须符合以下计算:
hashCode=1;
Iteratori=list.iterator();
while(i.hasNext()){
Objectobj=i.next();
hashCode=31*hashCode+(obj==null?0:obj.hashCode());
}
不仅仅散列值取决于list的内容,而且还规定了结合各个Element的散列值的特殊算法。(String类规定类似的算法用于计算String的散列值。)
编写自己的equals()和hashCode()方法
忽略缺省的equals()方法比较简单,但如果不违反对称(Symmetry)或传递性(Transitivity)需求,忽略已经忽略的equals()方法极其棘手。当忽略equals()时,您应该总是在equals()中包括一些Javadoc注释,以帮助那些希望能够正确扩展您的类的用户。
作为一个简单的例子,考虑以下类:
classA{
finalBsomeNonNullField;
CsomeOtherField;
intsomeNonStateField;
}
我们应如何编写该类的equals()的方法?这种方法适用于许多情况:
publicbooleanequals(Objectother){
//Notstrictlynecessary,butoftenagoodoptimization
if(this==other)
returntrue;
if(!(otherinstanceofA))
returnfalse;
AotherA=(A)other;
return
(someNonNullField.equals(otherA.someNonNullField))
&&((someOtherField==null)
?otherA.someOtherField==null
:someOtherField.equals(otherA.someOtherField)));
}
现在我们定义了equals(),我们必须以统一的方法来定义hashCode()。一种统一但并不总是有效的定义hashCode()的方法如下:
publicinthashCode(){return0;}
这种方法将生成大量的条目并显著降低HashMaps的性能,但它符合规范。一个更合理的hashCode()实施应该是这样:
publicinthashCode(){
inthash=1;
hash=hash*31+someNonNullField.hashCode();
hash=hash*31
+(someOtherField==null?0:someOtherField.hashCode());
returnhash;
}
注意:这两种实施都降低了类状态字段的equals()或hashCode()方法一定比例的计算能力。根据您使用的类,您可能希望降低superclass的equals()或hashCode()功能一部分计算能力。对于原始字段来说,在相关的封装类中有helper功能,可以帮助创建散列值,如Float.floatToIntBits。
编写一个完美的equals()方法是不现实的。通常,当扩展一个自身忽略了equals()的instantiable类时,忽略equals()是不切实际的,而且编写将被忽略的equals()方法(如在抽象类中)不同于为具体类编写equals()方法。关于实例以及说明的更详细信息请参阅EffectiveJavaProgrammingLanguageGuide,Item7(参考资料)。
有待改进?
将散列法构建到Java类库的根对象类中是一种非常明智的设计折衷方法--它使使用基于散列的容器变得如此简单和高效。但是,人们对Java类库中的散列算法和对象相等性的方法和实施提出了许多批评。java.util中基于散列的容器非常方便和简便易用,但可能不适用于需要非常高性能的应用程序。虽然其中大部分将不会改变,但当您设计严重依赖于基于散列的容器效率的应用程序时必须考虑这些因素,它们包括:
太小的散列范围。使用int而不是long作为hashCode()的返回类型增加了散列冲突的几率。
糟糕的散列值分配。短strings和小型integers的散列值是它们自己的小整数,接近于其它“邻近”对象的散列值。一个循规导矩(Well-behaved)的散列函数将在该散列范围内更均匀地分配散列值。
无定义的散列操作。虽然某些类,如String和List,定义了将其Element的散列值结合到一个散列值中使用的散列算法,但语言规范不定义将多个对象的散列值结合到新散列值中的任何批准的方法。我们在前面编写自己的equals()和hashCode()方法中讨论的List、String或实例类A使用的诀窍都很简单,但算术上还远远不够完美。类库不提供任何散列算法的方便实施,它可以简化更先进的hashCode()实施的创建。
当扩展已经忽略了equals()的instantiable类时很难编写equals()。当扩展已经忽略了equals()的instantiable类时,定义equals()的“显而易见的”方式都不能满足equals()方法的对称或传递性需求。这意味着当忽略equals()时,您必须了解您正在扩展的类的结构和实施详细信息,甚至需要暴露基本类中的机密字段,它违反了面向对象的设计的原则。
结束语
通过统一定义equals()和hashCode(),您可以提升类作为基于散列的集合中的关键字的使用性。有两种方法来定义对象的相等性和散列值:基于标识,它是Object提供的缺省方法;基于状态,它要求忽略equals()和hashCode()。当对象的状态更改时如果对象的散列值发生变化,确信当状态作为散列关键字使用时您不允许更更改其状态。
参考资料
您可以参阅本文在developerWorks全球站点上的英文原文.
参加本文的讨论论坛。(您还可以点击本文顶部或底部的讨论进入论坛。)
阅读BrianGoetz撰写的一整套Java理论和实践文章。尤其是2003年2月“Java理论与实践:变还是不变?,”它讨论使用可变对象作为散列关键字的危害。
JoshuaBloch杰作的第7和8部分Java编程语言指南,详细阐述围绕equals()和hashCode()的问题。
TonySintes在JavaWorld提供的本文中解释基于散列的容器是如何工作的以及如何使用equals()和hashCode()(2002年7月)。
在幻灯片中,新西兰奥克兰大学计算机科学系的RobertUzgalis介绍一些Java散列模式的批评意见,解释一些散列函数背后的问题。
MarkRoulo在自己的文章“如何避免陷阱和正确忽略java.lang.Object的方法”(JavaWorld,1999年1月)一文中提供了一些忽略equals()和hashCode()的实例程序代码。
新西兰坎特伯雷大学计算机科学系提供的这一份技术报告详细描述了whatmakesaneffectivehashfunction(PDF)。
IBM软件实验室软件工程师SreekanthIyer探讨了Java对象相等性的各种不同意义(developerWorks,2002年9月)。
JavaWorld(获得许可后才可再版)的提示略微谈到相等性比较的缺陷。
在developerWorksJava技术专区可以找到数百篇有关Java技术的参考资料。.
分享到:
相关推荐
本文将详细介绍 hashCode() 和 equals() 的本质区别和联系,并探讨在创建 Java 类时如何定义这些方法。 hashCode() 方法 hashCode() 方法是 Object 类中的一个方法,它返回对象的哈希码值。哈希码值是一个整数,它...
总结来说,`equals()` 和 `hashCode()` 在实际开发中的重写是提高代码质量、确保对象比较和哈希表操作正确性的关键。开发者需要根据业务需求来定制这两个方法,确保它们遵循上述原则,并与业务逻辑保持一致。
为了确保集合类如`HashSet`和`HashMap`的正确行为,`hashCode()`和`equals()`之间必须满足以下条件: - 如果`equals()`方法判定两个对象相等,则这两个对象的`hashCode()`值必须相同。 - 相反,如果两个对象的`...
在Java编程语言中,`equals()`, `...特别是在处理集合、映射等数据结构时,`equals()`和`hashCode()`的正确实现直接影响到查找效率和程序的正确运行。因此,理解并掌握这些基础方法是每个Java程序员必备的技能。
equals 方法和 hashCode 方法是 Java 语言中两个重要的方法,它们都是在 Object 类中定义的。equals 方法用于比较两个对象是否相等,而 hashCode 方法用于返回对象的哈希码。 在 Java 的 Object 类中,equals 方法...
hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。 使用hashCode()和equals() hashCode()方法被用来获取给定对象的整数。这个整数被用来确定对象被...
这是因为哈希表(如`HashSet`)依赖于`equals()`和`hashCode()`的一致性来正确地存储和查找对象。如果两个对象相等但它们的哈希码不同,那么这些对象在哈希表中可能会被存储在不同的位置,导致无法正确地查找和识别...
在进行集合操作时,遵循`equals()`和`hashCode()`的约定是非常重要的,因为它直接影响到集合的性能和正确性。 总的来说,通过IDEA的快捷键功能,我们可以快速、高效地完成`hashCode()`和`equals()`的重写工作,从而...
在Scala中,正确实现`equals`和`hashCode`方法对于集合操作至关重要,因为它们影响了对象在Set和Map中的存储和查找。这个处理器可能提供了一种自动化的方式来生成这些方法,避免了手动实现时可能出现的错误和不一致...
在Java编程中,`hashCode()`和`equals()`方法是两个非常重要的概念,它们对于理解和使用Java中的集合框架(尤其是`HashSet`和`HashMap`)至关重要。本文将详细介绍这两个方法的基本原理及其在Java中的实现方式。 ##...
类的实例可能会被序列化,同时为了在集合中正确地比较和存储这些实例,`equals`和`hashCode`方法会被重写。重写这些方法时,通常会基于类的业务关键属性来比较,以确保比较逻辑与业务逻辑一致。 总结来说,Java序列...
在Java编程语言中,`equals()` 和 `hashCode()` 方法是Object类中定义的基本方法,所有类都默认继承自Object类,因此每个Java对象都有这两个方法。这两个方法在处理集合类,尤其是Set接口的实现(如HashSet)时起着...
通过该案例代码,你可以学习如何在自己的类中正确重写equals()、hashCode()、toString()等方法,提高代码质量和可读性。 经验丰富的Java开发者:即使你已经有一定的Java开发经验,仍然值得深入了解Object类的使用。...
综上所述,正确理解和使用`equals`和`hashCode`方法对于编写高质量的Java程序至关重要。通过对这两个方法的合理覆盖,可以有效地提高数据结构(如`HashSet`和`HashMap`)的操作效率,同时也能更好地支持对象之间的...
在Java编程中,`equals()` 和 `hashCode()` 方法是Object类中的两个重要方法,它们在处理对象相等性以及在哈希表(如HashSet、HashMap)中起到关键作用。当自定义类时,有时需要根据业务逻辑重写这两个方法以满足...
同时,`equals`方法和`hashCode`方法应当一起重写,以确保对象相等时其哈希码也相等,这是HashMap等数据结构正确工作的前提。 总的来说,深入理解并正确实现`equals`方法是Java编程中的基础但关键的技能。这涉及到...
为了使对象能够被正确地存储在基于散列的集合(如`HashSet`、`HashMap`)中,`equals`方法和`hashCode`方法必须保持一致。这意味着如果两个对象根据`equals`方法被认为是相等的,那么它们的`hashCode`方法也必须返回...
### hashcode使用方法 在Java编程语言中,`hashCode` 方法是对象类 `Object` 的一部分,用于生成...开发者需要理解 `hashCode` 和 `equals` 方法之间的关系,并且在实现时遵循一定的原则,以确保程序的健壮性和效率。
深入理解Java中HashCode方法 Java中的hashCode方法是每个类都需要实现的重要方法之一,它的主要作用是将对象的数据...开发者需要了解hashCode方法的原理和实现方式,以便正确地使用hashCode方法来标识对象的唯一性。
总的来说,理解并正确使用 `equals()` 和 `hashCode()` 是Java编程中的关键技能,特别是在处理自定义类的比较和存储时。良好的实践可以帮助提高代码的可读性和性能,同时减少潜在的错误。在面试中,对这两个方法的...