Java提高学习之Object(4)
哈希码
问: hashCode()方法是用来做什么的?
答: hashCode()方法返回给调用者此对象的哈希码(其值由一个hash函数计算得来)。这个方法通常用在基于hash的集合类中,像java.util.HashMap,java.until.HashSet和java.util.Hashtable.
问: 在类中覆盖equals()的时候,为什么要同时覆盖hashCode()?
答: 在覆盖equals()的时候同时覆盖hashCode()可以保证对象的功能兼容于hash集合。这是一个好习惯,即使这些对象不会被存储在hash集合中。
问: hashCode()有什么一般规则?
答: hashCode()的一般规则如下:
- 在同一个Java程序中,对一个相同的对象,无论调用多少次
hashCode(),hashCode()返回的整数必须相同,因此必须保证equals()方法比较的内容不会更改。但不必在另一个相同的Java程序中也保证返回值相同。 - 如果两个对象用
equals()方法比较的结果是相同的,那么这两个对象调用hashCode()应该返回相同的整数值。 - 当两个对象使用
equals()方法比较的结果是不同的,hashCode()返回的整数值可以不同。然而,hashCode()的返回值不同可以提高哈希表的性能。
问: 如果覆盖了equals()却不覆盖hashCode()会有什么后果?
答: 当覆盖equals()却不覆盖hashCode()的时候,在hash集合中存储对象时就会出现问题。例如,参考代码清单2.
代码清单2:当hash集合只覆盖equals()时的问题
import java.util.HashMap;
import java.util.Map; final class Employee
{
private String name;
private int age; Employee(String name, int age)
{
this.name = name;
this.age = age;
} @Override
public boolean equals(Object o)
{
if (!(o instanceof Employee))
return false; Employee e = (Employee) o;
return e.getName().equals(name) && e.getAge() == age;
} String getName()
{
return name;
} int getAge()
{
return age;
}
} public class HashDemo
{
public static void main(String[] args)
{
Map<Employee, String> map = new HashMap<>();
Employee emp = new Employee("John Doe", );
map.put(emp, "first employee");
System.out.println(map.get(emp));
System.out.println(map.get(new Employee("John Doe", )));
}
}
代码清单2声明了一个Employee类,覆盖了equals()方法但是没有覆盖hashCode()。同时声明了一个一个HashDemo类,来演示将Employee作为键存储时时产生的问题。main()函数首先在实例化Employee之后创建了一个hashmap,将Employee对象作为键,将一个字符串作为值来存储。然后它将这个对象作为键来检索这个集合并输出结果。同样地,再通过新建一个具有相同内容的Employee对象作为键来检索集合,输出信息。
编译(javac HashDemo.java)并运行(java HashDemo)代码清单2,你将看到如下输出结果:
|
1
2
|
first employeenull |
如果hashCode()方法被正确的覆盖,你将在第二行看到first employee而不是null,因为这两个对象根据equals()方法比较的结果是相同的,根据上文中提到的规则2:如果两个对象用equals()方法比较的结果是相同的,那么这两个对象调用hashCode()应该返回相同的整数值。
问: 如何正确的覆盖hashCode()?
答: Joshua Bloch的《Effective Java》第八版中给出了一个四步法来正确的覆盖hashCode()。下面的步骤和Bloch的方法类似。
- 声明一个
int型的变量,命名为result(或者其他你喜欢的名字),然后初始化为一个不为零的常量(比如31)。使用一个不为零的常量会影响到所有的初始的哈希值(步骤2.1的结果)为零的值。【A nonzero value is used so that it will be affected by any initial fields whose hash value (computed in Step 2.1) is zero. 】如果初始的result为0的话,最后的哈希值不会被它影响到,所以冲突的几率会增加。这个非零result值是任意的。 - 对每一个对象中有意义的具体值(在
equals()中所涉及的值),f,进行以下步骤的处理:- 按照以下步骤计算f的基于
int型的哈希值hc:- 对于一个
boolean型变量,hc = f? 0 : 1;。 - 对于一个
byte,char,short,或者int型变量,hc = (int)f;. - 对于一个
long型变量,hc = (int) (f ^ (f >>> 32));.这个表达式是将long型变量作为32位(long型最多有32位)来计算的; - 对于一个
float型变量,hc = Float.floatToIntBits(f);. - 对于一个
double型变量,long l = Double.doubleToLongBits(f); hc = (int) (l ^ (l >>> 32));. - 对于引用类型的变量,如果类中的
equals()方法递归的调用equals()类比较成员变量,那么就递归调用hashCode();如果需要更复杂的比较,就计算这个值的“标准表示”来脚酸标准的哈希值;如果引用类型的值为null,f = 0. - 对于一个数组类型的引用,将每一个元素视为单独的变量,对于每一个有意义的值,调用对应的方法计算其哈希值,最后如步骤2.2的描述那样将所有的哈希值合并。
- 对于一个
- 计算
result = 37*result+hc,将所有的hc合并到哈希值中。乘法使哈希值取决于它的值的规则,当一个类中存在多种相似的值时,就增加了哈希表的离散性。 - 返回result。
- 完成
hashCode()之后,要确保相同的对象调用hashCode()得到相同的哈希值。
- 按照以下步骤计算f的基于
举例说明上面这个方法,代码清单3是代码清单2的第二个版本,它的Employee类重写了hashCode()。
代码清单3:正确地覆盖hashCode()
import java.util.HashMap;
import java.util.Map; final class Employee
{
private String name;
private int age; Employee(String name, int age)
{
this.name = name;
this.age = age;
} @Override
public boolean equals(Object o)
{
if (!(o instanceof Employee))
return false; Employee e = (Employee) o;
return e.getName().equals(name) && e.getAge() == age;
} String getName()
{
return name;
} int getAge()
{
return age;
} @Override
public int hashCode()
{
int result = ;
result = *result+name.hashCode();
result = *result+age;
return result;
}
} public class HashDemo
{
public static void main(String[] args)
{
Map<Employee, String> map = new HashMap<>();
Employee emp = new Employee("John Doe", );
map.put(emp, "first employee");
System.out.println(map.get(emp));
System.out.println(map.get(new Employee("John Doe", )));
}
}
代码清单3的Employee类中声明了两个在hashCode()都涉及到的值。覆盖的hashCode()方法首先初始化result为31,然后将String类型的name变量和int型的age变量的哈希值合并到result中,随后返回result。
编译(javac HashDemo.java)并运行(java HashDemo)代码清单3,你将看到如下输出结果:
|
1
2
|
first employeefirst employee |
Java提高学习之Object(4)的更多相关文章
- Java提高学习之Object类详解(1)
转自:http://www.importnew.com/10304.html 问:什么是Object类? 答:Object类存储在java.lang包中,是所有java类(Object类除外)的终极父 ...
- Java提高学习之Object(5)
字符串形式的表现 Q1:toString() 方法实现了什么功能?A1:toString() 方法将根据调用它的对象返回其对象的字符串形式,通常用于debug. Q2:当 toString() 方法没 ...
- Java提高学习之Object(3)
终止 问: finalize()方法是用来做什么的? 答: finalize()方法可以被子类对象所覆盖,然后作为一个终结者,当GC被调用的时候完成最后的清理工作(例如释放系统资源之类).这就是终止. ...
- Java提高学习之Object(2)
Equality 问:euqals()函数是用来做什么的? 答:equals()函数可以用来检查一个对象与调用这个equals()的这个对象是否相等. 问:为什么不用“==”运算符来判断两个对象是否相 ...
- java基础学习总结——Object类
一.Object类介绍
- 转载-java基础学习汇总
共2页: 1 2 下一页 Java制作证书的工具keytool用法总结 孤傲苍狼 2014-06-24 11:03 阅读:25751 评论:3 Java基础学习总结——Java对象的序列化和 ...
- Java提高篇(三二)-----List总结
前面LZ已经充分介绍了有关于List接口的大部分知识,如ArrayList.LinkedList.Vector.Stack,通过这几个知识点可以对List接口有了比较深的了解了.只有通过归纳总结的知识 ...
- java提高篇(四)-----理解java的三大特性之多态
面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...
- Java提高篇(三二)-----List总结
前面LZ已经充分介绍了有关于List接口的大部分知识,如ArrayList.LinkedList.Vector.Stack,通过这几个知识点能够对List接口有了比較深的了解了.仅仅有通过归纳总结的知 ...
随机推荐
- Django学习笔记(三)—— 型号 model
疯狂暑期学习 Django学习笔记(三)-- 型号 model 參考:<The Django Book> 第5章 1.setting.py 配置 DATABASES = { 'defaul ...
- SQL Server Reporting Services (SQLEXPRESS) 服务占用80端口
win7, 好多时候,看到system进程占用了80端口,这个是系统进程,不能直接结束.我们不知道这个进程的哪个服务占用了80端口,这里记录其中一个服务"SQL Server Reporti ...
- 解决 Boot Camp 虚拟机升级到 Windows 10 后 Parallels Desktop 不能识别的问题
最近几天 Win10 正式版开始推送了,对于喜欢折腾的博主,在第一时间就把 Mac 中 Boot Camp 从 Win7 升级到 Win10,初步体验还不错,等博主用过一段时间之后,再来给大家分享使用 ...
- 使用C#创建winform窗体,修改debugwen文件夹下exe应用程序的默认图标
在做一个接口程序是遇到的问题,记录一下: 在解决方案资源管理器上,右击项目名称——属性——点击图标和清单右边的的按纽——去Debug文件夹中找到自己的图标,打开.然后保存.
- (转载)log4net 组件详解
1.概述 log4net是.Net下一个非常优秀的开源日志记录组件.log4net记录日志的功能非常强大.它可以将日志分不同的等级,以不同的格式,输出到不同的媒介.本文主要是介绍如何在Visual S ...
- Oracle触发器Trigger2行级
create table trigger_t2( id int, name ), age int ); /* --创建一个before update的触发器-控制每一行,行级 --只有行级的才会有:n ...
- sql从某不连续的数字中将其分段并找出缺失的数字并分段
首先做准备数据 )) ') ') ') ') ') ') ') ') ') ') ') ') ') ') ') ') 将数据转换成应该处理的数据格式 ),colValue INT ) ) ,LEN(c ...
- Python同步数据库的数据到Neo4J
写了主要是步骤,如果疑问,请咨询QQ:5988628 Python版本采用2.7.X,默认的2.6.X后期会有问题,建议,一开始就升级Python.然后再安装pip. 访问数据库 sqlalchemy ...
- C#高效分页代码(不用存储过程)
首先创建一张表(要求ID自动编号): create table redheadedfile ( id ,), filenames ), senduser ), primary key(id) ) 然后 ...
- Ucenter整合Thinkphp 双向同步登录退出
1.整合初步工作: 1,安装Ucenter,完成后添加应用,填写要对接的网站地址 2,api , uc_client目录放置对接项目的根目录 3,通信对接,新建Ucenter组,confi文件填写在u ...