哈希原理
首先,在定义 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() 即可。

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 哈希码示例:
你自己的 hashCode() 和 equals()
你还可以自行创建 equals() 和 hashCode() 实现,但要小心并记住尽量减少哈希码冲突。下面是 Student 类中我们自己的 hashCode() 和 equals() 方法的示例:
import java.util.Date;
public class Student {
String surname;
String name;
String secondName;
Long birthday; // Gson/Jackson json 解析器和各种 orm 数据库使用 Long 而不是 long
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 哈希码示例
@Override
public int hashCode(){
//TODO:检查 null
//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("莎拉","康纳", "珍", null);
Student sarah2 = new Student("莎拉","康纳", "珍", new Date(1970, 01-1, 01));
Student sarah3 = new Student("莎拉","康纳", "珍", new Date(1959, 02-1, 28)); // 日期不存在
Student john = new Student("约翰","康纳", "凯尔", new Date(1985, 02-1, 28)); // 日期不存在
Student johnny = new Student("约翰","康纳", "凯尔", new Date(1985, 02-1, 28)); // 日期不存在
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
GO TO FULL VERSION