覆盖equals方法和hashCode方法看似简单,但其实不然,如果没有按照jdk的通用规范去覆盖,那么基于这些约定的类将可能无法正常工作,例如基于散列的集合类HashMap和HashSet.
对于值类,我们通常需要覆盖Object.equals方法,因为我们希望通过equals方法知道它们在逻辑上是否相等.相应的这个类的实例可以被用作map的key,或者set的元素的时候才会表现出预期的行为. 对于"值类",枚举是个例外,因为枚举的每个值都是个单例.
在覆盖equals时,必须遵守JavaSE Object的规范:自反性(reflective), 对称性 (symmetric),传递性(transitive),一致性(consistent),对于任何非null的引用x, x.equals(null)返回false. 对于每个覆盖类equals的类,都应该有相应的单元测试去检查是否有没有违反上述约定。下面是个简单的例子:
- public class PhoneNumber {
- private int countryCode;
- private String nationalNumber;
- public PhoneNumber(){
- super();
- }
- public PhoneNumber(int countryCode, String nationalNumber) {
- super();
- this.countryCode = countryCode;
- this.nationalNumber = nationalNumber;
- }
- /**
- * @return the countryCode
- */
- public int getCountryCode() {
- return countryCode;
- }
- /**
- * @param countryCode the countryCode to set
- */
- public void setCountryCode(int countryCode) {
- this.countryCode = countryCode;
- }
- /**
- * @return the nationalNumber
- */
- public String getNationalNumber() {
- return nationalNumber;
- }
- /**
- * @param nationalNumber the nationalNumber to set
- */
- public void setNationalNumber(String nationalNumber) {
- this.nationalNumber = nationalNumber;
- }
- @Override
- public boolean equals(Object o){
- if(this == o){
- return true;
- }
- if(!(o instanceof PhoneNumber)){
- return false;
- }
- PhoneNumber pn = (PhoneNumber) o;
- return this.countryCode == pn.getCountryCode()
- && ( this.nationalNumber == null ? pn.nationalNumber == null : this.nationalNumber.equals(pn.nationalNumber));
- }
- }
- import static org.junit.Assert.*;
- import org.junit.Test;
- public class PhoneNumberTest {
- @Test
- public void testEqualsReflexive(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- assertTrue(pn1.equals(pn1));
- PhoneNumber pn2 = new PhoneNumber();
- assertTrue(pn2.equals(pn2));
- }
- @Test
- public void testEqualsSymmetric(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- assertEquals(pn1.equals(pn2), pn2.equals(pn1));
- }
- @Test
- public void testEqualsTransitive(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- PhoneNumber pn3 = new PhoneNumber(86, new String("12345"));
- assertTrue(pn1.equals(pn2));
- assertTrue(pn2.equals(pn3));
- assertTrue(pn1.equals(pn3));
- }
- @Test
- public void testEqualsConsistent(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- for(int i=0; i<10 ; i++){
- assertTrue(pn1.equals(pn2));
- }
- }
- @Test
- public void testEqualsWithNull(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- assertFalse(pn1.equals(null));
- }
- }
当然还有一些实现高质量equals方法的诀窍:
1. 使用==操作符检查"参数是否为正确的引用"
2. 使用instanceof检查类型
3. 把参数转化为正确的类型
4. 选择逻辑比较的关键域,注意比较的顺序,primitive的比较可以放在前面,或者最有可能不一致性的域
5. 如果有double,float类型,用Double.compare,Float.compare比较
6. 覆盖equals重要覆盖hashCode
如前文所述,在覆盖了equals方法的类中,也必须覆盖hashCode方法。否则违反了Object.hashCode的通用约定会导致该类无法和基于散列的集合(HashMap,HashSet和HashTable)一起正常使用。
如下约定内容摘自Object规范:
1. 在应用程序中,只要对象的euqals方法的比较操作所用的信息没有修改,那么对于同一个对象的调用多次hashCode,必须始终如一返回同一个哈希值。
2. 如果两个对象通过equals比较相等,那么它们的哈希值相同。
3. 如果两个对象通过euqals比较不等,他们的哈希值可能相同,取决于hashCode的实现,由此散列表的性能也会有区别。
以前面的PhoneNumber类为例,编写了如下的测试用例:
- @Test
- public void testHashCode(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- Map<PhoneNumber,String> map = new HashMap<PhoneNumber,String>();
- map.put(pn1, "12345");
- assertNotNull(map.get(pn2));
- }
发现测试失败了,但是两个对象通过equals比较是相等的。由于并没有覆盖hashCode方法导致两个相等的对象不能获得相同的散列码。根据约定重写hashCode:
- @Override
- public int hashCode(){
- int result = 17;
- result = 31 * result + countryCode;
- if(nationalNumber != null)
- result = 31 * result + nationalNumber.hashCode();
- return result;
- }
好的散列函数通常倾向于"为不相等的对象产生不相等的散列码", 否则会引起冲突,使散列表想链表退化。计算是可以把冗余的域排除在外。注意不要试图从散列码计算中排除关键域来提高性能。
相关推荐
Java解惑系列之一--equals和==之间究竟有什么区别 稍微学过一些java的同学都可能在网络上看到这样的一道题: 在java语言当中,equals和==之间究竟有什么区别? 而这道题的答案也是千篇一律: equals是用来比较对象...
equals() 是 Java 中的一个方法,用于比较对象是否相等。它是 Object 类的方法,在许多类中都可以使用。 在默认情况下,equals() 方法用于比较两个对象的引用是否相等,即判断它们是否指向同一个内存地址。这是通过...
java代码-使用java解决实现Student类的equals重载函数的源代码 ——学习参考资料:仅用于个人学习使用!
JavaCV also comes with helper classes and methods on top of OpenCV and FFmpeg to facilitate their integration to the Java platform. Here is a small demo program demonstrating the most frequently ...
Java语言深入_equals
java代码-== equals测试
能够加强对java中equals与==区别的理解。
java_equals用法,用来熟悉重写equals方法的
计算机后端-Java-Java核心基础-第24章 集合01 23. 关于hashCode()和equals()的重写.avi
第1章 Java概述 1 1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6...
计算机后端-Java-Java核心基础-第14章 面向对象06 14. 重写equals().avi
【Java面试题】equals与==的区别
计算机后端-Java-Java核心基础-第14章 面向对象06 13. equals()的使用.avi
java8 集合源码分析 java基础复习 [TOC] 一、集合 1.Iterator 2.Collection 2.1 List--->有序、有索引、元素可重复 1.ArrayList: 底层是数组结构、查询快、增删慢、不同步 添加第一个元素的时候,创建默认个数是...
计算机后端-Java-Java核心基础-第14章 面向对象06 15. 总结==与equals().avi
计算机后端-Java-Java核心基础-第14章 面向对象06 17. equals()练习2:代码实现.avi
计算机后端-Java-Java核心基础-第14章 面向对象06 16. equals()练习1:代码实现.avi
java代码-array 的 sort fill equals 用法
Java重写equals同时需要重写hashCode的代码说明,以及如何重写hashCode方法,此代码演示按照effective java书籍说明的重写思路。代码中演示了使用集合存储对象,并且对象作为key,需重写equals和hashCode.