哈希原理

首先,在定义 Java 哈希码之前,我们需要了解什么是哈希以及它的用途。哈希是对某些数据应用哈希函数的过程。哈希函数只是一个数学函数。不用担心不理解!“数学”并不总是意味着“复杂”。这里哈希仅仅表示我们有一些数据和某种规则,将数据映射到一组字符(代码)。 例如,它可以是十六进制密码。我们将一些任意大小的数据作为输入,并对其应用哈希函数。作为输出,我们得到一个固定大小的数据,比如 32 个字符。通常,这种函数会将一大段数据转换为一个小的整数值。该函数工作的结果称为哈希码。 哈希函数广泛用于密码学,还用于其他一些领域。哈希函数可以不同,但它们全都具有特定的属性:
  • 特定对象具有特定的哈希码。
  • 如果两个对象相等,则它们的哈希码相同。反过来则不正确。
  • 如果哈希码不同,则对象肯定不相等。
  • 不同的对象可能具有相同的哈希码。不过,这种事可能性极低。如果这样,则会发生冲突,这种情况可能会导致丢失数据。
“合适的”哈希函数会将冲突的概率降至最低。

Java 中的哈希码

在 Java 中,哈希函数通常关联 hashCode() 方法。准确地说,将哈希函数应用于 Object 的结果是哈希码。每个 Java 对象都有一个哈希码。一般来说,哈希码是一个由 Object 类的 hashCode() 方法计算出来的数字。通常,程序员会为其对象重写该方法以及与 hashCode() 相关的 equals() 方法,以便更高效地处理特定数据。 hashCode() 方法返回一个 int(4 字节)值,它是对象的数字表示。例如,集合使用此哈希码以更有效地存储数据,从而更快地进行访问。 默认情况下,对象的 hashCode() 函数返回存储对象的内存单元的编号。因此,如果未对应用程序代码进行任何更改,则该函数应返回相同的值。如果代码稍有变化,哈希码值也会发生变化。 Java 中使用的哈希码是什么?首先,Java 哈希码助力程序更快运行。例如,如果我们比较某种类型的两个对象 o1 和 o2,则运算 o1.equals(o2) 所花时间大约是 o1.hashCode() == o2.hashCode() 的 20 倍。

Java equals()

在父类 Object 中,除了 hashCode() 方法之外,还有 equals(),即用于检查两个对象是否相等的函数。此函数的默认实现只是检查两个对象的链接是否相等。 equals() 和 hashCode() 签有合约,所以如果你重写其中一个,则应该重写另外一个,以免破坏这个合约。

实现 hashCode() 方法

示例

现在创建一个类 Character,该类带有一个字段 name。之后,我们创建 Character 类的两个对象 character1 和 character2 并将它们设置为相同的名称。如果我们使用 Object 类的默认 hashCode() 和 equals() 方法,我们肯定会得到不同的,不相等的对象。这就是 Java 中的哈希码的工作原理。这两个对象将具有不同的哈希码,因为它们位于不同的内存单元中,equals() 操作结果将为 false。

import java.util.Objects;

public class Character {
    private String Name;

    public Character(String name) {
        Name = name;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    } 

    public static void main(String[] args) {
        Character character1 = new Character("Arnold");
        System.out.println(character1.getName());
        System.out.println(character1.hashCode());
        Character character2 = new Character("Arnold");
        System.out.println(character2.getName());
        System.out.println(character2.hashCode());
        System.out.println(character2.equals(character1));
    }
}
运行程序所得的结果如下:

Arnold
1595428806
Arnold
1072408673
false
控制台中的两个 10 位数字是哈希码。如果我们想要拥有相同名称的相同对象怎么办?我们应该做什么? 答案是:我们应该为 Character 类重写 Object 类的 hashCode() 和 equals() 方法。我们可以在 IDEA IDE 中自动完成,只需按键盘上的 alt + insert 并选择 Generate -> equals() and hashCode() 即可。 什么是 Java hashCode() - 2通过示例得到下列代码:

import java.util.Objects;

public class Character {
    private String Name;

    public Character(String name) {
        Name = name;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Character)) return false;

        Character character = (Character) o;

        return getName() != null ? getName().equals(character.getName()) : character.getName() == null;
    }

    @Override
    public int hashCode() {
        return getName() != null ? getName().hashCode() : 0;
    }

    public static void main(String[] args) {
        Character character1 = new Character("Arnold");
        System.out.println(character1.getName());
        System.out.println(character1.hashCode());
        Character character2 = new Character("Arnold");
        System.out.println(character2.getName());
        System.out.println(character2.hashCode());
        System.out.println(character2.equals(character1));
    }
}
运行此代码的结果:

Arnold
1969563338
Arnold
1969563338
true
所以现在程序将我们的对象识别为相等并且它们具有相同的哈希码。 Java 中的哈希码是什么? - 2

Java 哈希码示例:

你自己的 hashCode() 和 equals()

你还可以自行创建 equals() 和 hashCode() 实现,但要小心并记住尽量减少哈希码冲突。下面是 Student 类中我们自己的 hashCode() 和 equals() 方法的示例:

import java.util.Date;

public class Student {
   String surname;
   String name;
   String secondName;
   Long birthday; // Long instead of long is used by Gson/Jackson json parsers and various orm databases

   public Student(String surname, String name, String secondName, Date birthday ){
       this.surname = surname;
       this.name = name;
       this.secondName = secondName;
       this.birthday = birthday == null ? 0 : birthday.getTime();
   }
//Java hashcode example
   @Override
   public int hashCode(){
       //TODO: check for nulls
       //return surname.hashCode() ^ name.hashCode() ^ secondName.hashCode() ^ (birthday.hashCode());
       return (surname + name + secondName + birthday).hashCode();
   }
   @Override
   public boolean equals(Object other_) {
       Student other = (Student)other_;
       return (surname == null || surname.equals(other.surname) )
               && (name == null || name.equals(other.name))
               && (secondName == null || secondName.equals(other.secondName))
               && (birthday == null || birthday.equals(other.birthday));
   }
}
Main 类工作展示:

import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;

public class Main {
   static HashMap<Student, Integer> cache = new HashMap<Student, Integer>(); // <person, targetPriority>

   public static void main(String[] args) {
       Student sarah1 = new Student("Sarah","Connor", "Jane", null);
       Student sarah2 = new Student("Sarah","Connor", "Jane", new Date(1970, 01-1, 01));
       Student sarah3 = new Student("Sarah","Connor", "Jane", new Date(1959, 02-1, 28)); // date not exists
       Student john = new Student("John","Connor", "Kyle", new Date(1985, 02-1, 28)); // date not exists
       Student johnny = new Student("John","Connor", "Kyle", new Date(1985, 02-1, 28)); // date not exists
       System.out.println(john.hashCode());
       System.out.println(johnny.hashCode());
       System.out.println(sarah1.hashCode());
       System.out.println();
       cache.put(sarah1, 1);
       cache.put(sarah2, 2);
       cache.put(sarah3, 3);
       System.out.println(new Date(sarah1.birthday));
       System.out.println();
       cache.put(john, 5);
       System.out.println(cache.get(john));
       System.out.println(cache.get(johnny));
       cache.put(johnny, 7);
       System.out.println(cache.get(john));
       System.out.println(cache.get(johnny));
   }
}

哈希码用途是什么?

首先,哈希码助力程序更快运行。例如,如果我们比较某种类型的两个对象 o1 和 o2,则运算 o1.equals(o2) 所花时间大约是 o1.hashCode() == o2.hashCode() 的 20 倍。在 Java 中,哈希原理支持一些流行的集合,例如 HashMap、HashSet 和 HashTable

结论

每个 Java 对象都有从 Object 类继承的 hashCode() 和 equals() 方法。要获得良好的工作平等机制,你最好为自己的类重写 hashcode() 和 equals() 方法。使用哈希码助力程序更快运行