Java提高学习之Object(4)
2016-01-15 19:10
411 查看
哈希码
问: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", 29); map.put(emp, "first employee"); System.out.println(map.get(emp)); System.out.println(map.get(new Employee("John Doe", 29))); } }
代码清单2声明了一个
Employee类,覆盖了
equals()方法但是没有覆盖
hashCode()。同时声明了一个一个
HashDemo类,来演示将
Employee作为键存储时时产生的问题。
main()函数首先在实例化
Employee之后创建了一个hashmap,将
Employee对象作为键,将一个字符串作为值来存储。然后它将这个对象作为键来检索这个集合并输出结果。同样地,再通过新建一个具有相同内容的
Employee对象作为键来检索集合,输出信息。
编译(
javac HashDemo.java)并运行(
java HashDemo)代码清单2,你将看到如下输出结果:
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()得到相同的哈希值。
举例说明上面这个方法,代码清单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 = 31; result = 37*result+name.hashCode(); result = 37*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", 29); map.put(emp, "first employee"); System.out.println(map.get(emp)); System.out.println(map.get(new Employee("John Doe", 29))); } }
代码清单3的
Employee类中声明了两个在
hashCode()都涉及到的值。覆盖的
hashCode()方法首先初始化
result为
31,然后将
String类型的
name变量和
int型的
age变量的哈希值合并到
result中,随后返回
result。
编译(
javac HashDemo.java)并运行(
java HashDemo)代码清单3,你将看到如下输出结果:
相关文章推荐
- Java提高学习之Object(3)
- Object-C--->Swift之(五)数组和字典
- Objective-C是动态运行时语言的个人理解
- hibernate class cast exception from object to ...
- Java提高学习之Object(2)
- Java提高学习之Object类详解(1)
- Java Object...可变长度的参数(Varargs)详解
- Objective-C属性特性
- Objective-C KVC和KVO的使用
- Swift 懒加载(lazy) 和 Objective-C 懒加载的区别
- 浅谈Scala 2.8的包对象(package object)
- Object-C--->Swift之(四)强大的Switch
- xjc-beyond sliding windows-Object Localization by Efficient Subwindow Search 论文的算法和知识点
- json字符串转换为JSONObject和JSONArray
- Qt QObject
- 在Swift中,如何像Objective-C定义可选接口?
- Objective-C中使用不定参数个数的方法调用
- FLASH动画-- SWFObject
- Objective-c学习日总结之与C的区别及类与对象的定义,类的声明及调用方法
- Swift语言调用Objective-c