具有相同散列值的 objects 应该相等吗?
Should to objects which have the same hash be equal?
在下面的示例中,我创建了两个具有完全相同内部结构的 objects。两者都只携带值 1 作为实例变量。我的想法是,如果我采用 e1
的散列,它应该与 e2
的散列相同,因此 e1.equals(e2)
应该 return 为真。
class EqualsChecker {
public static void main(String[] args) {
Elem e1 = new Elem(1);
Elem e2 = new Elem(1);
System.out.println(e1); // EqualsChecker$Elem@6ff3c5b5
System.out.println(e2); // EqualsChecker$Elem@3764951d
System.out.println("e1.equals(e2): " + e1.equals(e2)); // returns false
}
static class Elem {
private int v;
public Elem(int i) {
this.v = i;
}
}
}
这里为什么equals
return false?我想我在下面的草图中有中间情况:
equals(Object)
的默认实现检查两个对象是否是同一个实例(即它们是 ==
)。如果你想要一些不同的逻辑,你必须自己实现它。请注意,如果这样做,您还应该实现自己的 hashCode()
,这样两个相等的对象也将具有匹配的哈希码。例如:
class Elem {
private int v;
@Override
public boolean equals(final Object o) {
if (o == null || this.getClass() != o.getClass()) {
return false;
}
Elem elem = (Elem) o;
return this.v == elem.v;
}
@Override
public int hashCode() {
return this.v;
}
}
从https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#hashCode--
看以下几点
- 只要在 Java 应用程序的执行过程中对同一个对象多次调用,hashCode 方法必须始终 return 相同的整数,前提是没有在 equals 比较中使用的信息对象被修改。从一个应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。
- 如果根据 equals(Object) 方法两个对象相等,则对这两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
- 不要求如果根据 equals(java.lang.Object) 方法两个对象不相等,则对两个对象中的每一个调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
- 在相当实用的情况下,class Object 定义的 hashCode 方法为不同的对象执行 return 不同的整数。 (hashCode 可能会或可能不会在某个时间点实现为对象内存地址的某些函数。)
现在,查看以下代码及其输出:
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 511833308
employee3.hashCode(): 511833308
false
employee2.hashCode(): 1297685781
由于 employee3
指向与 employee1
相同的对象,因此您将获得相同的哈希码,而 employee2
指向不同的对象(尽管它们具有相同的哈希码)内容,关键字,new
将在内存中创建一个单独的对象),因此,您可能很少获得 employee2
与提到的 point#4 相同的哈希码以上来自文档说明:As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects.
您必须以某种方式重写 hashCode
方法,使具有相同内容的两个对象 return 具有相同的哈希码,例如
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((code == null) ? 0 : code.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 128107556
employee3.hashCode(): 128107556
false
employee2.hashCode(): 128107556
上面给出的 hashCode
的实现为 employee1
和 employee2
生成相同的哈希码,即使 equals
returns false
(检查上面从文档中提到的 point#3。
覆盖 hashCode
的错误方法甚至可能导致相同的对象 return 使用不同的哈希码,例如
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((code == null) ? 0 : (int) (code.length() * (Math.random() * 100)));
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee1.hashCode() again: " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 66066760
employee1.hashCode() again: 66069457
employee3.hashCode(): 66073797
false
employee2.hashCode(): 66074882
这是覆盖 hashCode
的错误方法,因为在执行 Java 应用程序期间多次调用同一对象 hashCode
必须始终 return相同的整数(检查上面文档中提到的 point#1)。
现在,查看以下代码及其输出:
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyEmployee other = (MyEmployee) obj;
if (code == null) {
if (other.code != null)
return false;
} else if (!code.equals(other.code))
return false;
return true;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 511833308
employee3.hashCode(): 511833308
true
employee2.hashCode(): 1297685781
因为 employee1.equals(employee2)
returns true
,哈希码也应该 returned 相同(检查 point#2上面从文档中提到的)。但是,employee1
和 employee2
的哈希码值不同,这是不正确的。这种差异是因为我们没有覆盖 hashCode
方法。因此,无论何时覆盖 equals
,您也应该以正确的方式覆盖 hashCode
。
最后,下面给出了 hashCode
和 equals
的正确实现方式:
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((code == null) ? 0 : code.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyEmployee other = (MyEmployee) obj;
if (age != other.age)
return false;
if (code == null) {
if (other.code != null)
return false;
} else if (!code.equals(other.code))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 128107556
employee3.hashCode(): 128107556
true
employee2.hashCode(): 128107556
您需要重写equals
方法,否则Object
的equals
方法将用于比较两个实例。
@Override
public boolean equals(Object that) {
if (this == that) return true;
if (that instanceof Elem) {
Elem thatElem = (Elem) that;
return thatElem.v == this.v;
}
return false;
}
在下面的示例中,我创建了两个具有完全相同内部结构的 objects。两者都只携带值 1 作为实例变量。我的想法是,如果我采用 e1
的散列,它应该与 e2
的散列相同,因此 e1.equals(e2)
应该 return 为真。
class EqualsChecker {
public static void main(String[] args) {
Elem e1 = new Elem(1);
Elem e2 = new Elem(1);
System.out.println(e1); // EqualsChecker$Elem@6ff3c5b5
System.out.println(e2); // EqualsChecker$Elem@3764951d
System.out.println("e1.equals(e2): " + e1.equals(e2)); // returns false
}
static class Elem {
private int v;
public Elem(int i) {
this.v = i;
}
}
}
这里为什么equals
return false?我想我在下面的草图中有中间情况:
equals(Object)
的默认实现检查两个对象是否是同一个实例(即它们是 ==
)。如果你想要一些不同的逻辑,你必须自己实现它。请注意,如果这样做,您还应该实现自己的 hashCode()
,这样两个相等的对象也将具有匹配的哈希码。例如:
class Elem {
private int v;
@Override
public boolean equals(final Object o) {
if (o == null || this.getClass() != o.getClass()) {
return false;
}
Elem elem = (Elem) o;
return this.v == elem.v;
}
@Override
public int hashCode() {
return this.v;
}
}
从https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#hashCode--
看以下几点- 只要在 Java 应用程序的执行过程中对同一个对象多次调用,hashCode 方法必须始终 return 相同的整数,前提是没有在 equals 比较中使用的信息对象被修改。从一个应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。
- 如果根据 equals(Object) 方法两个对象相等,则对这两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
- 不要求如果根据 equals(java.lang.Object) 方法两个对象不相等,则对两个对象中的每一个调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
- 在相当实用的情况下,class Object 定义的 hashCode 方法为不同的对象执行 return 不同的整数。 (hashCode 可能会或可能不会在某个时间点实现为对象内存地址的某些函数。)
现在,查看以下代码及其输出:
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 511833308
employee3.hashCode(): 511833308
false
employee2.hashCode(): 1297685781
由于 employee3
指向与 employee1
相同的对象,因此您将获得相同的哈希码,而 employee2
指向不同的对象(尽管它们具有相同的哈希码)内容,关键字,new
将在内存中创建一个单独的对象),因此,您可能很少获得 employee2
与提到的 point#4 相同的哈希码以上来自文档说明:As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects.
您必须以某种方式重写 hashCode
方法,使具有相同内容的两个对象 return 具有相同的哈希码,例如
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((code == null) ? 0 : code.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 128107556
employee3.hashCode(): 128107556
false
employee2.hashCode(): 128107556
上面给出的 hashCode
的实现为 employee1
和 employee2
生成相同的哈希码,即使 equals
returns false
(检查上面从文档中提到的 point#3。
覆盖 hashCode
的错误方法甚至可能导致相同的对象 return 使用不同的哈希码,例如
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((code == null) ? 0 : (int) (code.length() * (Math.random() * 100)));
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee1.hashCode() again: " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 66066760
employee1.hashCode() again: 66069457
employee3.hashCode(): 66073797
false
employee2.hashCode(): 66074882
这是覆盖 hashCode
的错误方法,因为在执行 Java 应用程序期间多次调用同一对象 hashCode
必须始终 return相同的整数(检查上面文档中提到的 point#1)。
现在,查看以下代码及其输出:
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyEmployee other = (MyEmployee) obj;
if (code == null) {
if (other.code != null)
return false;
} else if (!code.equals(other.code))
return false;
return true;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 511833308
employee3.hashCode(): 511833308
true
employee2.hashCode(): 1297685781
因为 employee1.equals(employee2)
returns true
,哈希码也应该 returned 相同(检查 point#2上面从文档中提到的)。但是,employee1
和 employee2
的哈希码值不同,这是不正确的。这种差异是因为我们没有覆盖 hashCode
方法。因此,无论何时覆盖 equals
,您也应该以正确的方式覆盖 hashCode
。
最后,下面给出了 hashCode
和 equals
的正确实现方式:
class MyEmployee {
String code;
String name;
int age;
public MyEmployee(String code, String name, int age) {
super();
this.code = code;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((code == null) ? 0 : code.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyEmployee other = (MyEmployee) obj;
if (age != other.age)
return false;
if (code == null) {
if (other.code != null)
return false;
} else if (!code.equals(other.code))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class Main {
public static void main(String[] args) {
MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
MyEmployee employee3 = employee1;
System.out.println(employee1.equals(employee3));
System.out.println("employee1.hashCode(): " + employee1.hashCode());
System.out.println("employee3.hashCode(): " + employee3.hashCode());
System.out.println(employee1.equals(employee2));
System.out.println("employee2.hashCode(): " + employee2.hashCode());
}
}
输出:
true
employee1.hashCode(): 128107556
employee3.hashCode(): 128107556
true
employee2.hashCode(): 128107556
您需要重写equals
方法,否则Object
的equals
方法将用于比较两个实例。
@Override
public boolean equals(Object that) {
if (this == that) return true;
if (that instanceof Elem) {
Elem thatElem = (Elem) that;
return thatElem.v == this.v;
}
return false;
}