• Java提高学习之Object(4)


    哈希码

    问: hashCode()方法是用来做什么的?

    答: hashCode()方法返回给调用者此对象的哈希码(其值由一个hash函数计算得来)。这个方法通常用在基于hash的集合类中,像java.util.HashMap,java.until.HashSetjava.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()时的问题

     1 import java.util.HashMap;
     2 import java.util.Map;
     3  
     4 final class Employee
     5 {
     6    private String name;
     7    private int age;
     8  
     9    Employee(String name, int age)
    10    {
    11   this.name = name;
    12   this.age = age;
    13    }
    14  
    15    @Override
    16    public boolean equals(Object o)
    17    {
    18   if (!(o instanceof Employee))
    19  return false;
    20  
    21   Employee e = (Employee) o;
    22   return e.getName().equals(name) && e.getAge() == age;
    23    }
    24  
    25    String getName()
    26    {
    27   return name;
    28    }
    29  
    30    int getAge()
    31    {
    32   return age;
    33    }
    34 }
    35  
    36 public class HashDemo
    37 {
    38    public static void main(String[] args)
    39    {
    40   Map<Employee, String> map = new HashMap<>();
    41   Employee emp = new Employee("John Doe", 29);
    42   map.put(emp, "first employee");
    43   System.out.println(map.get(emp));
    44   System.out.println(map.get(new Employee("John Doe", 29)));
    45    }
    46 }

        代码清单2声明了一个Employee类,覆盖了equals()方法但是没有覆盖hashCode()。同时声明了一个一个HashDemo类,来演示将Employee作为键存储时时产生的问题。main()函数首先在实例化Employee之后创建了一个hashmap,将Employee对象作为键,将一个字符串作为值来存储。然后它将这个对象作为键来检索这个集合并输出结果。同样地,再通过新建一个具有相同内容的Employee对象作为键来检索集合,输出信息。

    编译(javac HashDemo.java)并运行(java HashDemo)代码清单2,你将看到如下输出结果:

    1
    2
    first employee
    null

    如果hashCode()方法被正确的覆盖,你将在第二行看到first employee而不是null,因为这两个对象根据equals()方法比较的结果是相同的,根据上文中提到的规则2:如果两个对象用equals()方法比较的结果是相同的,那么这两个对象调用hashCode()应该返回相同的整数值。

    问: 如何正确的覆盖hashCode()

    答: Joshua Bloch的《Effective Java》第八版中给出了一个四步法来正确的覆盖hashCode()。下面的步骤和Bloch的方法类似。

    1. 声明一个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. 】如果初始的result0的话,最后的哈希值不会被它影响到,所以冲突的几率会增加。这个非零result值是任意的。
    2. 对每一个对象中有意义的具体值(在equals()中所涉及的值),f,进行以下步骤的处理:
      1. 按照以下步骤计算f的基于int型的哈希值hc
        1. 对于一个boolean型变量,hc = f? 0 : 1;
        2. 对于一个byte,char,short,或者int型变量,hc = (int)f;.
        3. 对于一个long型变量,hc = (int) (f ^ (f >>> 32));.这个表达式是将long型变量作为32位(long型最多有32位)来计算的;
        4. 对于一个float型变量,hc = Float.floatToIntBits(f);.
        5. 对于一个double型变量,long l = Double.doubleToLongBits(f); hc = (int) (l ^ (l >>> 32));.
        6. 对于引用类型的变量,如果类中的equals()方法递归的调用equals()类比较成员变量,那么就递归调用hashCode();如果需要更复杂的比较,就计算这个值的“标准表示”来脚酸标准的哈希值;如果引用类型的值为nullf = 0.
        7. 对于一个数组类型的引用,将每一个元素视为单独的变量,对于每一个有意义的值,调用对应的方法计算其哈希值,最后如步骤2.2的描述那样将所有的哈希值合并。
      2. 计算result = 37*result+hc,将所有的hc合并到哈希值中。乘法使哈希值取决于它的值的规则,当一个类中存在多种相似的值时,就增加了哈希表的离散性。
      3. 返回result。
      4. 完成hashCode()之后,要确保相同的对象调用hashCode()得到相同的哈希值。

    举例说明上面这个方法,代码清单3是代码清单2的第二个版本,它的Employee类重写了hashCode()

    代码清单3:正确地覆盖hashCode()

     1 import java.util.HashMap;
     2 import java.util.Map;
     3  
     4 final class Employee
     5 {
     6    private String name;
     7    private int age;
     8  
     9    Employee(String name, int age)
    10    {
    11       this.name = name;
    12       this.age = age;
    13    }
    14  
    15    @Override
    16    public boolean equals(Object o)
    17    {
    18       if (!(o instanceof Employee))
    19          return false;
    20  
    21       Employee e = (Employee) o;
    22       return e.getName().equals(name) && e.getAge() == age;
    23    }
    24  
    25    String getName()
    26    {
    27       return name;
    28    }
    29  
    30    int getAge()
    31    {
    32       return age;
    33    }
    34  
    35    @Override
    36    public int hashCode()
    37    {
    38       int result = 31;
    39       result = 37*result+name.hashCode();
    40       result = 37*result+age;
    41       return result;
    42    }
    43 }
    44  
    45 public class HashDemo
    46 {
    47    public static void main(String[] args)
    48    {
    49       Map<Employee, String> map = new HashMap<>();
    50       Employee emp = new Employee("John Doe", 29);
    51       map.put(emp, "first employee");
    52       System.out.println(map.get(emp));
    53       System.out.println(map.get(new Employee("John Doe", 29)));
    54    }
    55 }

        代码清单3的Employee类中声明了两个在hashCode()都涉及到的值。覆盖的hashCode()方法首先初始化result31,然后将String类型的name变量和int型的age变量的哈希值合并到result中,随后返回result

    编译(javac HashDemo.java)并运行(java HashDemo)代码清单3,你将看到如下输出结果:

    1
    2
    first employee
    first employee
  • 相关阅读:
    maven项目编译漏掉src/main/java下的xml配置文件
    读《架构探险——从零开始写Java Web框架》
    使用generator自动生成Mybatis映射配置文件
    git项目添加.gitigore文件
    git-bash下, 启动sshd
    git-bash.exe参数
    少估了一分, 还算不错
    python常用库
    Linux下python pip手动安装笔记
    python学习笔记
  • 原文地址:https://www.cnblogs.com/sunfie/p/5134300.html
Copyright © 2020-2023  润新知