我是靠谱客的博主 有魅力水壶,这篇文章主要介绍认识Object中的几个经常需要覆盖的方法——hashCode方法 1,引言 2,分析,现在分享给大家,希望可以做个参考。

学习Java少不了对Object的认知,所有类都会继承它的属性,真正的超类。这一个系列,我会对Object中的几个方法,也就是我们自定义类的时候需要重写的几个方法做一个介绍。下面是这一个系列的主要内容:

  • equals方法
  • hashCode方法
  • toString方法
  • clone方法
  • 自定义类时考虑实现Comparable接口

本系列内容源于对《Effective Java》中文第二版第8条到第12条的学习记录。所有内容的准确性均以原书为准。

 

 

1,引言

如果没有记错,我们在介绍equals的时候,最后总结的时候我们说到:

如果重写了equals方法,最好或者必须也重写hashCode 方法;这里,我们先不介绍重写hashCode 方法的原因,我们先来看一个例子:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
public class TestClass {     public static void main(String[] args) {         HashMap<String, Person> idToPerson = new HashMap<>();         HashMap<Person, String> personToId = new HashMap<>();                  idToPerson.put("ID2020", new Person(26,"why","hfut"));         personToId.put(new Person(26,"why","hfut"), "ID2020");         System.out.println("idToPerson, id=ID2020:"+idToPerson.get("ID2020"));         System.out.println("personToId, person=Person(26,"why","hfut"):"+personToId.get(new Person(26,"why","hfut")));              }


其中的Person(内容请查看上一篇博客)重写了equals方法,没有重写hashCode方法;上面的代码我们期望输出的结果是分别输出对应键的值数据,单实际的运行结果如下:

第二个通过Person对象查找的值居然是null,于是我们突然想到,HashMap是基于散列的集合,也就是说会最终把键的值转化为对象的散列值(hashCode返回值)来进行查找对应的值数据。那我们又想,那String类肯定重写了hashCode方法:

这就是String重写的hashCode方法,上面的注释也介绍了最终的hashCode值的计算公式。所以我们我们明白了为什么在定义类的时候也必须重写hashCode方法,因为不这样做,它没有办法和HashMap,HashSet以及HashTable这样的基于散列的集合一起很好的工作(作为值无所谓,不能作为键)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**      * Returns a hash code for this string. The hash code for a      * {@code String} object is computed as      * <blockquote><pre>      * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]      * </pre></blockquote>      * using {@code int} arithmetic, where {@code s[i]} is the      * <i>i</i>th character of the string, {@code n} is the length of      * the string, and {@code ^} indicates exponentiation.      * (The hash value of the empty string is zero.)      *      * @return  a hash code value for this object.      */  public int hashCode() {         int h = hash;         if (h == 0 && value.length > 0) {             char val[] = value;             for (int i = 0; i < value.length; i++) {                 h = 31 * h + val[i];             }             hash = h;         }         return h;     }

 

2,分析

 

好的,下面就来看看重写hashCode方法需要注意的一些事项,首先先看官方文档:

返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

hashCode 的常规协定是:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

 

现在,我们就这三条,对我们的Person类改进,重写其hashCode方法:

第一步:hashCode返回一个和对象信息无关的int值

复制代码
1
2
3
4
5
 @Override     public int hashCode() {         // TODO Auto-generated method stub         return 1;     }

我们再次运行上面的测试代码,结果如下:

 

现在正常了,但是我们也会发现,这个哈希值是与类绑定的,所以会出现下面的问题,先修改Person代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package hfut.edu; /**  * Date:2018年10月1日 上午11:05:45 Author:why  */ public class Person {     int age;     String name;     String sex;     public Person(int age, String name, String sex) {         super();         this.age = age;         this.name = name;         this.sex = sex;     }     public int getAge() {         return age;     }     public void setAge(int age) {         this.age = age;     }     public String getName() {         return name;     }     public void setName(String name) {         this.name = name;     }     public String getSex() {         return sex;     }     public void setSex(String sex) {         this.sex = sex;     }     @Override     public boolean equals(Object obj) {         // TODO Auto-generated method stub         if (!(obj instanceof Person))             return false;         Person p = (Person) obj;         return this.age == p.age && this.name.equals(p.name) && this.sex.equals(p.sex);     }      @Override     public int hashCode() {         // TODO Auto-generated method stub         return 1;     } }

然后再看测试代码:

我们发现了一个严重的问题,就是即便是键发生了变化,对应的值还是没有变,这就是这种方式带来的验证弊端。

 

第二步:如何获取与对象绑定的哈希值

(1)选择一个非零的整数值作为初始默认值(String类中是0)

复制代码
1
2
  /** Cache the hash code for the string */     private int hash; // Default to 0

(2)针对对象中的每一个关键域完成:

         a,如果该域为boolean类型,则(var?1:0)

         b,如果该域为byte,char,short或者int类型,则(int)var

         c,如果该域为long类型,则(int)(var^(var>>>32))

         d,如果该域为float类型,则Float.floatToIntBits(var)

         e,如果该域为double类型,则Double.doubleToLongBits(var),再执行步骤c

         f,如果该域为引用类型,递归调用hashCode方法

         g,如果该域为一个数组,每一个数组元素也做单独处理

得到int值c

 

下面就我们的Person类,重写其hashCode方法如下:

复制代码
1
2
3
4
5
6
7
8
9
10
@Override     public int hashCode() {         // TODO Auto-generated method stub             int  hash=1;         hash=hash*31+this.age;         hash=hash*31+this.name.hashCode();         hash=hash*31+this.sex.hashCode();         return hash;     }

还是非常简单的,这个时候我们来再次测试一下上面的代码,结果如下:

这样,就不会出现上述的问题了,下面就是要测试其是否满足上面的三条约束了。主要测试就是第二条,通过equals方法比较两个对象相同时,那么,它们的hashCode方法得到的值一定相同,这里就不验证了,在最后,我写一个成员变量多一点的类,并重写继承而来的hashCode方法,熟悉一下上面的计算规则,

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package hfut.edu; /**  * Date:2018年10月1日 下午6:10:22 Author:why  */ public class HashCodeTest {     boolean var0;     int var1;     char var2;     byte var3;     short var4;     float var5;     double var6;     long var7;     public HashCodeTest( boolean var0, int var1, char var2, byte var3, short var4, float var5, double var6,             long var7) {         super();         this.var0 = var0;         this.var1 = var1;         this.var2 = var2;         this.var3 = var3;         this.var4 = var4;         this.var5 = var5;         this.var6 = var6;         this.var7 = var7;     }     @Override     public int hashCode() {         // TODO Auto-generated method stub         int hash=1;         hash=hash*31+(var0?1:0);         hash=hash*31+var1;         hash=hash*31+(int)var2;         hash=hash*31+(int)var3;         hash=hash*31+(int)var4;         hash=hash*31+Float.floatToIntBits(var5);         hash=hash*31+(int)(Double.doubleToLongBits(var6)/(int)(Math.pow(2, 32)));         hash=hash*31+(int)(var7/(int)(Math.pow(2, 32)));                 return hash;                  } }

这里面我用的都是基本类型,因为其他的应用类型都可以用这些组合而成。这里面的系数之所以用31是因为其为一个奇素数,可以通过移位和减法来代替乘法,也即:

        31*var==(var<<5)-var   (var为int值)

到这里,关于hashCode方法的内容就介绍的差不多了。

最后

以上就是有魅力水壶最近收集整理的关于认识Object中的几个经常需要覆盖的方法——hashCode方法 1,引言 2,分析的全部内容,更多相关认识Object中内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(73)

评论列表共有 0 条评论

立即
投稿
返回
顶部