说明:
本系列博客是本人在工作中遇到的一些问题的整理,其中有些资料来源网络博客,有些信息来自出版的书籍,掺杂一些个人的猜想及验证,总结,主要目的是方便知识的查看,并非纯原创。本系列博客会不断更新。原创不容易,支持原创。对于参考的一些其他博客,会尽量把博客地址列在博客的后面,以方便知识的查看。
本篇博客可以看做是《Effective Java中文版第2版》第三章(对于所有对象都通用的方法)第九条(覆盖equals时总要覆盖hashCode)的读书笔记,其中掺杂了一些个人的想法及验证。
我们来看下Object类中hashCode方法的实现:
public native int hashCode();
这是一个本地方法
java规范对其描述是这样的:
1、在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
2、如果两个对象根据equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生相同的整数结果。
3、如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中国任意一个对象的hashCode方法,不一定产生不同的整数结果。但是给不相等的对象产生不同的整数结果,有可能提高散列表的性能。
书中的一个例子:
/**
* Created with IntelliJ IDEA.
* User: yejunwu123@gmail.com
* Date: 2014-08-20 13:27
* Description:
*/
public class PhoneNumber {
private short areaCode;
private short prefix;
private short lineNumber;
public PhoneNumber(int areaCode,int prefix,int lineNumber){
this.areaCode = (short)areaCode;
this.prefix = (short)prefix;
this.lineNumber = (short)lineNumber;
}
}
此处没有覆盖hashCode和equals方法,看下测试:
/**
* Created with IntelliJ IDEA.
* User: yejunwu123@gmail.com
* Date: 2014-08-19 23:41
* Description:
*/
public class HashCodeTest {
public static void main(String[] args) {
PhoneNumber phoneNumber1 = new PhoneNumber(1,2,3);
PhoneNumber phoneNumber2 = new PhoneNumber(1,2,3);
System.out.println("phoneNumber1.hashCode:" + phoneNumber1.hashCode());
System.out.println("phoneNumber2.hashCode:" + phoneNumber2.hashCode());
}
}
控制台输出:
phoneNumber1.hashCode:24355087
phoneNumber2.hashCode:5442986
类的不同实例,他们的hashCode不同
我们来加上equals方法:
@Override
public boolean equals(Object obj) {
if (obj == this){
return true;
}
if (!(obj instanceof PhoneNumber)){
return false;
}
PhoneNumber p = (PhoneNumber)obj;
return this.areaCode == p.areaCode
&& this.prefix == p.prefix
&& this.lineNumber == p.lineNumber;
}
看下如下测试:
public class HashCodeTest {
public static void main(String[] args) {
PhoneNumber phoneNumber1 = new PhoneNumber(1,2,3);
PhoneNumber phoneNumber2 = new PhoneNumber(1,2,3);
System.out.println("phoneNumber1.hashCode:" + phoneNumber1.hashCode());
System.out.println("phoneNumber2.hashCode:" + phoneNumber2.hashCode());
System.out.println("------------------");
System.out.println("phoneNumber1.equals(phoneNumber2):" + phoneNumber1.equals(phoneNumber2));
Map<PhoneNumber,String> map = new HashMap<PhoneNumber, String>();
map.put(phoneNumber1,"zhangsan");
System.out.println("map contains phoneNumber1:" + map.containsKey(phoneNumber2));
String name = map.get(phoneNumber2);
System.out.println("name:" + name);
}
}
创建PhoneNumber 的两个实例,先比较他们的equals,然后将第一个实例加入hashMap,然后判断HashMap的containsKey方法及get方法(入参是第二个实例),控制台输出如下:
phoneNumber1.hashCode:24355087
phoneNumber2.hashCode:5442986
------------------
phoneNumber1.equals(phoneNumber2):true
map contains phoneNumber1:false
name:null
导致这个原因是因为覆盖了equals方法,没有同时覆盖hashCode方法,HashMap实现在put、get、containsKey等方法时会计算key的hash值,具体HashMap的原理可以参看博客:
http://www.cnblogs.com/xwdreamer/archive/2012/06/03/2532832.html
已经讲得很详细了,不重复劳动了。
书中提出的一种简单的hashCode生成方式如下:
1、把某个非0的常数值,比如说17,保存在一个名为result的int类型的变量中;
2、对于对象中的每个关键域f(equals方法中涉及的每个域),完成以下步骤:
a.为该域计算int类型的散列码c:
ⅰ.如果该类型是boolean类型,则计算(f ? 1 : 0);
ⅱ.如果该域是byte、char、short、int类型,则计算(int)f;
ⅲ.如果该域是long类型,则计算(int)(f ^ (f >>> 32));
ⅳ.如果该域是float类型,则计算Float.floatToIntBits(f);
ⅴ.如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤
2.a.ⅲ,为得到的long类型的值计算散列值;
ⅵ.如果该域是一个对象引用,并且该类的equals方法通过递归的调用equals的方式
来比较这个域,则同样为这个域递归的调用hashCode,如果这个域的值为null,则
返回0;
ⅶ.如果该域是一个数组,则要把每一个元素当做单独的域来处理。如果数组域中
的每个元素都很重要,可以使用Arrays.hashCode方法。
b.按照如下公式,把2.a中计算得到的散列码c合并到result中:
result = 31 * result + c;
3、返回result。
以上规则能记住当然最好,但是工作中我们用的IDE往往为我们提供了这些功能,以下代码是在IDEA中自动生成的:
@Override
public int hashCode() {
int result = (int) areaCode;
result = 31 * result + (int) prefix;
result = 31 * result + (int) lineNumber;
return result;
}
跟以上规则是类似的。
再来运行下之前的测试:
phoneNumber1.hashCode:1026
phoneNumber2.hashCode:1026
------------------
phoneNumber1.equals(phoneNumber2):true
map contains phoneNumber1:true
name:zhangsan
我们来看下如下的测试:
/**
* Created with IntelliJ IDEA.
* User: yejunwu123@gmail.com
* Date: 2014-08-20 15:25
* Description:
*/
public class PhoneNumberTest {
public static void main(String[] args) {
PhoneNumber phoneNumber1 = new PhoneNumber(1,2,3);
Map<PhoneNumber,String> map = new HashMap<PhoneNumber, String>();
map.put(phoneNumber1,"zhangsan");
System.out.println("map contains PhoneNumber(1,2,3) before update:" + map.containsKey(new PhoneNumber(1,2,3)));
System.out.println("----------------------");
for (PhoneNumber phoneNumber : map.keySet()){
System.out.println("before update hash code:" + phoneNumber.hashCode());
phoneNumber.setAreaCode((short) 4);
System.out.println("after update hash code:" + phoneNumber.hashCode());
}
for (PhoneNumber phoneNumber : map.keySet()){
System.out.println(phoneNumber.getAreaCode() + " " + phoneNumber.getPrefix() + " " + phoneNumber.getLineNumber() + " " + phoneNumber.hashCode());
}
System.out.println("map contains PhoneNumber(1,2,3) after update:" + map.containsKey(new PhoneNumber(1,2,3)));
System.out.println("##############");
System.out.println("map contains PhoneNumber(4,2,3) after update:" + map.containsKey(new PhoneNumber(4,2,3)));
}
}
将phoneNumber1 加入HashMap,然后判断map.containsKey(new PhoneNumber(1,2,3)),然后对HashMap遍历,修改key的areacode属性,以改变key的hashCode值,然后再去判断map.containsKey(new PhoneNumber(1,2,3))和map.containsKey(new PhoneNumber(4,2,3))
看下输出结果:
map contains PhoneNumber(1,2,3) before update:true
----------------------
before update hash code:1026
after update hash code:3909
4 2 3 3909
map contains PhoneNumber(1,2,3) after update:false
##############
map contains PhoneNumber(4,2,3) after update:false
后面两次比较都是返回false
原因:
for (PhoneNumber phoneNumber : map.keySet()){
System.out.println("before update hash code:" + phoneNumber.hashCode());
phoneNumber.setAreaCode((short) 4);
System.out.println("after update hash code:" + phoneNumber.hashCode());
}
这段代码执行过后,因为map中key的areaCode值已经修改,在判断map.containsKey(new PhoneNumber(1,2,3))时,由于equals方法返回false导致contains方法返回false;而在判断map.containsKey(new PhoneNumber(4,2,3))返回false是因为map的key的hashCode与new PhoneNumber(4,2,3)的hashCode值不一样,这点在前文提到的博客中有说明,map中的entry中的key会缓存计算后的hash值,详细信息可以参看上文提到的博客。
hashCode和equals方法还是很重要的,在理解hibernate的数据库一致性与实体一致性时有涉及到。
相关推荐
hashCode 的用法详解 hashCode 是一种用于查找和排序的机制,在数据结构中 plays a crucial role。下面我们将对 hashCode 的用法进行详细的解释。 hashCode 的作用 hashCode 的主要作用是用于查找和排序。在查找...
### 深入理解 HashCode 方法 #### 一、HashCode 的基本概念与作用 在 Java 编程语言中,`HashCode` 是一个非常重要且基础的概念。简单来说,`HashCode` 是一个整数值,用于快速定位对象的位置。在 Java 中,每一个...
在Java编程语言中,`equals()` 和 `hashCode()` 方法是Object类中的两个核心方法,所有类都默认继承自Object类。这两个方法在处理对象比较和集合操作时起着至关重要的作用。当我们创建自定义类并需要对对象进行精确...
本文介绍了Java语言不直接支持关联数组,可以使用任何对象作为一个索引的数组,但在根Object类中使用 hashCode()方法明确表示期望广泛使用HashMap。理想情况下基于散列的容器提供有效插入和有效检索;直接在对象模式...
equals 与 hashCode 方法讲解 equals 方法和 hashCode 方法是 Java 语言中两个重要的方法,它们都是在 Object 类中定义的。equals 方法用于比较两个对象是否相等,而 hashCode 方法用于返回对象的哈希码。 在 Java...
"Java中Hashcode的作用" Hashcode是Java编程语言中一个非常重要的概念,它在equals方法中扮演着关键角色。在Java中,每个对象都具有一个独特的Hashcode,它可以用来标识对象的身份。但是Hashcode是什么?它是如何...
在Java编程语言中,`hashCode()` 和 `equals()` 是两个非常重要的方法,它们主要用于对象的比较和哈希表(如HashMap)的操作。标题提到的"HashCode相同equals不同的2位字符集合算法"涉及到的是一个特定场景:两个...
### Java中`hashCode()`与`equals()`方法详解 #### 前言 在Java编程语言中,`hashCode()`和`equals()`方法是非常重要的概念,它们不仅对于深入理解Java内存管理至关重要,也是实现自定义类的关键部分之一。本文将...
本主题将深入探讨如何利用反射技术绕过编译器的一些限制,并介绍hashcode在高级应用中的用法。 首先,让我们理解反射的基本概念。在Java中,反射提供了一种方式,使我们能够在运行时动态地获取类的信息(如类名、...
本篇将深入探讨ArrayList与HashSet的区别,并分析Hashcode在其中的作用。 ArrayList是基于动态数组实现的,它提供了按索引访问元素的能力,就像在数组中一样。由于内部维护了一个数组,ArrayList保证了元素的顺序性...
equals()和hashcode()这两个方法都是从object类中继承过来的。当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法.
### 复写`hashCode()`方法与`equals()`方法 在Java编程中,`hashCode()`方法与`equals()`方法是对象比较中的两个非常重要的方法。它们主要用于判断对象是否相等以及对象的散列值,这对于集合类(如`HashSet`)来说...
在Java编程语言中,`hashCode()`和`equals()`方法是对象身份验证的关键组成部分,它们主要用于对象的比较和哈希表(如HashMap、HashSet等)的操作。理解这两个方法的工作原理对于编写高效和可靠的代码至关重要。 ...
深入解析Java对象的equals()和hashCode()的使用 在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是自己设计其中一个,就要设计另外一个。在多数情况下,这两个函数是不用考虑的,直接使用它们...
hashCode() 和 equals() 的本质区别和联系 Java 中的每个对象都有 hashCode() 和 equals() 方法,这两个方法的正确实现对于 Java 开发人员来说是非常重要的。本文将详细介绍 hashCode() 和 equals() 的本质区别和...
### HashCode的作用详解 #### 一、HashCode的基本概念 在Java中,`hashCode()` 方法是 `Object` 类的一个重要成员方法,它返回一个整数,这个整数通常用来表示对象的哈希值。哈希值在Java集合框架中扮演着至关重要...
在Java编程语言中,`equals()` 和 `hashCode()` 方法是对象的基本组成部分,它们在很多场景下都发挥着至关重要的作用。这两个方法与对象的相等性比较和哈希表(如HashMap、HashSet)的运作紧密相关。这篇博客将深入...
在Java编程语言中,`equals()`, `hashCode()` 和 `toString()` 是三个非常重要的方法,它们主要用于对象的比较、哈希存储以及打印对象信息。这三个方法是Java对象的基础特性,对于理解和开发高质量的Java程序至关...
在Java编程语言中,`hashCode()` 和 `equals()` 方法是两个非常重要的概念,尤其是在处理对象比较和哈希表(如 `HashMap` 或 `HashSet`)时。这两个方法来源于 `Object` 类,是所有Java类的基类,因此,每个自定义类...