最近我通读了这个 Developer Works Document。
该文档是关于定义hashCode()
和equals()
有效和正确的,但是我无法弄清楚为什么我们需要覆盖这两种方法。
我怎样才能决定有效地实施这些方法?
最近我通读了这个 Developer Works Document。
该文档是关于定义hashCode()
和equals()
有效和正确的,但是我无法弄清楚为什么我们需要覆盖这两种方法。
我怎样才能决定有效地实施这些方法?
Joshua Bloch 谈有效的 Java
您必须在覆盖 equals() 的每个类中覆盖 hashCode()。不这样做将导致违反 Object.hashCode() 的一般约定,这将阻止您的类与所有基于哈希的集合(包括 HashMap、HashSet 和 Hashtable)一起正常运行。
让我们尝试通过一个示例来理解它,如果我们覆盖equals()
而不覆盖hashCode()
并尝试使用Map
.
假设我们有一个这样的类,并且MyClass
如果它们相等,则两个对象importantField
相等(使用eclipsehashCode()
并由equals()
eclipse 生成)
public class MyClass {
private final String importantField;
private final String anotherField;
public MyClass(final String equalField, final String anotherField) {
this.importantField = equalField;
this.anotherField = anotherField;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((importantField == null) ? 0 : importantField.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MyClass other = (MyClass) obj;
if (importantField == null) {
if (other.importantField != null)
return false;
} else if (!importantField.equals(other.importantField))
return false;
return true;
}
}
想象一下你有这个
MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");
仅覆盖equals
如果 onlyequals
被覆盖,那么当您myMap.put(first,someValue)
第一次调用时将散列到某个存储桶,当您调用myMap.put(second,someOtherValue)
它时将散列到其他存储桶(因为它们有不同的hashCode
)。因此,尽管它们是相等的,但由于它们没有散列到同一个桶,因此映射无法实现,它们都留在映射中。
equals()
尽管如果我们 override没有必要override hashCode()
,让我们看看在这种特殊情况下会发生什么,我们知道 的两个对象MyClass
如果它们importantField
相等但我们不 override 是相等的equals()
。
仅覆盖hashCode
如果您只覆盖hashCode
,那么当您调用myMap.put(first,someValue)
它时,首先计算它hashCode
并将其存储在给定的存储桶中。然后,当您调用它时,应该根据地图文档myMap.put(second,someOtherValue)
将第一个替换为第二个,因为它们是相等的(根据业务要求)。
但问题是 equals 没有重新定义,所以当映射散列second
并遍历桶时,如果有一个对象k
是second.equals(k)
真的,它不会找到任何second.equals(first)
对象false
。
希望很清楚
诸如HashMap
和之类的集合HashSet
使用对象的哈希码值来确定它应该如何存储在集合中,并且再次使用哈希码以在其集合中定位对象。
哈希检索是一个两步过程:
hashCode()
)equals()
)equals()
这是一个关于为什么我们应该覆盖和的小例子hashcode()
。
考虑一个Employee
有两个字段的类:年龄和姓名。
public class Employee {
String name;
int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof Employee))
return false;
Employee employee = (Employee) obj;
return employee.getAge() == this.getAge()
&& employee.getName() == this.getName();
}
// commented
/* @Override
public int hashCode() {
int result=17;
result=31*result+age;
result=31*result+(name!=null ? name.hashCode():0);
return result;
}
*/
}
现在创建一个类,将Employee
对象插入 aHashSet
并测试该对象是否存在。
public class ClientTest {
public static void main(String[] args) {
Employee employee = new Employee("rajeev", 24);
Employee employee1 = new Employee("rajeev", 25);
Employee employee2 = new Employee("rajeev", 24);
HashSet<Employee> employees = new HashSet<Employee>();
employees.add(employee);
System.out.println(employees.contains(employee2));
System.out.println("employee.hashCode(): " + employee.hashCode()
+ " employee2.hashCode():" + employee2.hashCode());
}
}
它将打印以下内容:
false
employee.hashCode(): 321755204 employee2.hashCode():375890482
现在取消注释hashcode()
方法,执行相同的,输出将是:
true
employee.hashCode(): -938387308 employee2.hashCode():-938387308
现在你能明白为什么如果认为两个对象相等,它们的hashcode也必须相等吗?否则,您将永远无法找到该对象,因为 Object 类中的默认
hashcode方法几乎总是为每个对象提供一个唯一编号,即使该equals()
方法被覆盖以使两个或多个对象被视为相等. 如果对象的hashcode不反映这一点,那么对象的平等程度并不重要。所以再来一次:如果两个对象相等,那么它们的
hashcode也必须相等。
您必须在覆盖 equals() 的每个类中覆盖 hashCode()。不这样做将导致违反 Object.hashCode() 的一般约定,这将阻止您的类与所有基于哈希的集合(包括 HashMap、HashSet 和 Hashtable)一起正常运行。
来自有效的 Java,作者 Joshua Bloch
通过定义equals()
和hashCode()
一致,您可以提高类作为基于哈希的集合中的键的可用性。正如 hashCode 的 API 文档所解释的那样:“支持这种方法是为了哈希表的好处,例如由java.util.Hashtable
.
关于如何有效实现这些方法的问题的最佳答案是建议您阅读Effective Java的第 3 章。
身份不是平等。
==
测试身份。equals(Object obj)
方法比较相等测试(即我们需要通过覆盖方法来判断相等)为什么我需要覆盖 Java 中的 equals 和 hashCode 方法?
首先我们要了解equals方法的使用。
为了识别两个对象之间的差异,我们需要重写 equals 方法。
例如:
Customer customer1=new Customer("peter");
Customer customer2=customer1;
customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
------------------------------
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.
------------------------------
Now I have overriden Customer class equals method as follows:
@Override
public boolean equals(Object obj) {
if (this == obj) // it checks references
return true;
if (obj == null) // checks null
return false;
if (getClass() != obj.getClass()) // both object are instances of same class or not
return false;
Customer other = (Customer) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference
return false;
return true;
}
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
Insteady identify the Object equality by JVM, we can do it by overring equals method.
customer1.equals(customer2); // returns true by our own logic
现在 hashCode 方法很容易理解。
hashCode 生成整数以便将对象存储在HashMap、HashSet等数据结构中。
假设我们有Customer
上面的重写 equals 方法,
customer1.equals(customer2); // returns true by our own logic
当我们将对象存储在桶中时使用数据结构(桶是文件夹的一个花哨的名称)。如果我们使用内置哈希技术,对于以上两个客户,它会生成两个不同的哈希码。所以我们将同一个相同的对象存储在两个不同的地方。为避免此类问题,我们还应基于以下原则重写 hashCode 方法。
简而言之,Object 中的 equals 方法检查引用是否相等,因为当属性相等时,类的两个实例在语义上仍然可能相等。例如,在将对象放入使用 equals 和哈希码的容器(如HashMap和Set )时,这一点很重要。假设我们有一个像这样的类:
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
}
我们创建两个具有相同id的实例:
Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");
没有覆盖等于我们得到:
正确的?好吧,也许,如果这是你想要的。但是假设我们希望具有相同 id 的对象是同一个对象,无论它是否是两个不同的实例。我们覆盖等号(和哈希码):
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
@Override
public boolean equals(Object other) {
if (other instanceof Foo) {
return ((Foo)other).id.equals(this.id);
}
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}
至于实现 equals 和 hashcode 我可以推荐使用Guava 的辅助方法
为什么我们重写equals()
方法
在 Java 中,我们不能重载 ==、+=、-+ 等运算符的行为方式。他们以某种方式行事。因此,让我们在这里关注运算符 == 。
运算符 == 如何工作。
它检查我们比较的 2 个引用是否指向内存中的同一个实例。仅当这 2 个引用代表内存中的相同实例时,运算符==
才会解析为 true。
所以现在让我们考虑下面的例子
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
因此,假设在您的程序中,您在不同的地方构建了 2 个 Person 对象,并且您希望对它们进行比较。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
从业务角度来看,这两个对象看起来一样吗?对于 JVM,它们是不一样的。由于它们都是使用new
关键字创建的,因此这些实例位于内存中的不同段中。因此运算符 == 将返回false
但是如果我们不能覆盖 == 运算符,我们怎么能对 JVM 说我们希望这两个对象被视为相同。有.equals()
方法在起作用。
您可以覆盖equals()
以检查某些对象是否具有相同的特定字段的值以被视为相等。
您可以选择要比较的字段。如果我们说 2 个 Person 对象当且仅当它们具有相同的年龄和相同的名称时它们是相同的,那么 IDE 将创建类似以下的内容来自动生成 equals()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
让我们回到我们之前的例子
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
所以我们不能重载 == 运算符来以我们想要的方式比较对象,但是 Java 给了我们另一种方式,equals()
方法,我们可以根据需要覆盖它。
但是请记住,如果我们没有.equals()
在我们的类中提供我们的自定义版本(又名覆盖),那么.equals()
来自 Object 类和==
操作符的预定义的行为将完全相同。
从 Object 继承的默认equals()
方法将检查两个比较的实例在内存中是否相同!
为什么我们重写hashCode()
方法
Java中的一些数据结构,如HashSet,HashMap基于应用于这些元素的散列函数存储它们的元素。哈希函数是hashCode()
如果我们可以选择覆盖.equals()
方法,那么我们也必须选择覆盖hashCode()
方法。这是有原因的。
从 Object 继承的默认实现hashCode()
认为内存中的所有对象都是唯一的!
让我们回到那些哈希数据结构。这些数据结构有一个规则。
HashSet 不能包含重复值,HashMap 不能包含重复键
HashSet 在后台使用 HashMap 实现,其中 HashSet 的每个值都作为键存储在 HashMap 中。
所以我们必须了解 HashMap 是如何工作的。
简单来说,HashMap 是一个包含一些桶的原生数组。每个桶都有一个linkedList。在该linkedList 中存储了我们的密钥。HashMap 通过应用方法为每个键找到正确的linkedList hashCode()
,然后遍历该linkedList 的所有元素,equals()
并对这些元素中的每一个应用方法以检查该元素是否已包含在那里。不允许有重复的键。
当我们在 HashMap 中放入一些东西时,键存储在这些链接列表之一中。该键将存储在哪个linkedList中由该hashCode()
键上的方法的结果显示。因此,如果key1.hashCode()
结果为 4,则该 key1 将存储在数组的第 4 个存储桶中,即存在的linkedList 中。
默认情况下hashCode()
,方法为每个不同的实例返回不同的结果。如果我们有默认值equals()
,它的行为类似于 ==,它将内存中的所有实例视为不同的对象,我们没有任何问题。
但是在我们之前的示例中,我们说如果 Person 实例的年龄和名称匹配,我们希望它们被认为是相等的。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
现在让我们创建一个映射来将这些实例存储为键,并将一些字符串作为对值
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
在 Person 类中,我们没有重写hashCode
方法,但我们已经重写了equals
方法。由于默认hashCode
为不同的 java 实例提供了不同的结果,person1.hashCode()
并且person2.hashCode()
很有可能产生不同的结果。
我们的地图可能以不同链接列表中的人结束。
这违背了 HashMap 的逻辑
一个 HashMap 不允许有多个相等的键!
但是我们现在有,原因是hashCode()
从 Object Class 继承的默认值是不够的。不是在我们覆盖了equals()
Person 类的方法之后。
这就是为什么我们必须在重写hashCode()
方法之后重写方法的原因equals
。
现在让我们解决这个问题。让我们重写我们的hashCode()
方法以考虑相同的字段equals()
,即age, name
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
现在让我们再次尝试将这些键保存在我们的 HashMap 中
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode()
并且person2.hashCode()
肯定会是一样的。假设它是0。
HashMap 将转到存储桶 0 并在其中 LinkedList 将 person1 保存为值为“1”的键。对于第二个放置的 HashMap 足够智能,当它再次进入存储桶 0 以保存值为“2”的 person2 键时,它将看到那里已经存在另一个相等的键。所以它会覆盖以前的密钥。所以最终只有 person2 键会存在于我们的 HashMap 中。
现在我们符合 Hash Map 的规则,即不允许多个相等的键!
让我用非常简单的话来解释这个概念。
首先从更广泛的角度来看,我们有集合,hashmap 是集合中的数据结构之一。
要理解为什么我们必须重写 equals 和 hashcode 方法,如果需要首先了解什么是 hashmap 和什么是做什么的。
hashmap 是一种数据结构,它以数组方式存储数据的键值对。比方说 a[],其中 'a' 中的每个元素都是一个键值对。
此外,上述数组中的每个索引都可以是链表,从而在一个索引处具有多个值。
现在为什么要使用哈希图?
如果我们必须在一个大数组中搜索,那么搜索每个数组是否效率不高,那么哈希技术告诉我们让我们用一些逻辑预处理数组并根据该逻辑对元素进行分组,即散列
EG:我们有数组 1,2,3,4,5,6,7,8,9,10,11,我们应用哈希函数 mod 10,因此 1,11 将组合在一起。因此,如果我们必须在前一个数组中搜索 11,那么我们将不得不迭代整个数组,但是当我们对它进行分组时,我们会限制我们的迭代范围,从而提高速度。为简单起见,用于存储上述所有信息的数据结构可以视为二维数组
现在除了上面的 hashmap 还告诉它不会在其中添加任何 Duplicates。这就是我们必须覆盖equals和hashcode的主要原因
所以当它说解释hashmap的内部工作时,我们需要找到hashmap有哪些方法以及它是如何遵循我上面解释的上述规则的
所以 hashmap 有一个叫做 put(K,V) 的方法,根据 hashmap 它应该遵循上面的规则来有效地分布数组并且不添加任何重复
所以 put 的作用是它首先为给定的键生成哈希码,以决定该值应该进入哪个索引。如果该索引处没有任何内容,那么如果那里已经存在某些东西,那么新值将被添加到那里然后应该在该索引处的链表末尾添加新值。但请记住,不应根据哈希图的所需行为添加重复项。所以假设你有两个整数对象 aa=11,bb=11。
作为从对象类派生的每个对象,比较两个对象的默认实现是比较引用而不是对象内部的值。因此,在上述情况下,尽管语义上相等,但相等性测试都将失败,并且可能存在具有相同哈希码和相同值的两个对象,从而创建重复项。如果我们覆盖,那么我们可以避免添加重复项。你也可以参考细节工作
import java.util.HashMap;
public class Employee {
String name;
String mobile;
public Employee(String name,String mobile) {
this.name = name;
this.mobile = mobile;
}
@Override
public int hashCode() {
System.out.println("calling hascode method of Employee");
String str = this.name;
int sum = 0;
for (int i = 0; i < str.length(); i++) {
sum = sum + str.charAt(i);
}
return sum;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
System.out.println("calling equals method of Employee");
Employee emp = (Employee) obj;
if (this.mobile.equalsIgnoreCase(emp.mobile)) {
System.out.println("returning true");
return true;
} else {
System.out.println("returning false");
return false;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Employee emp = new Employee("abc", "hhh");
Employee emp2 = new Employee("abc", "hhh");
HashMap<Employee, Employee> h = new HashMap<>();
//for (int i = 0; i < 5; i++) {
h.put(emp, emp);
h.put(emp2, emp2);
//}
System.out.println("----------------");
System.out.println("size of hashmap: "+h.size());
}
}
hashCode()
:
如果您只覆盖哈希码方法,则不会发生任何事情,因为它总是hashCode
为每个对象返回一个新对象作为 Object 类。
equals()
:
如果您只覆盖 equals 方法,如果a.equals(b)
为 true,则意味着hashCode
a 和 b 必须相同,但这不会发生,因为您没有覆盖该hashCode
方法。
注意: hashCode()
Object 类的方法总是hashCode
为每个对象返回一个新的。
因此,当您需要在基于散列的集合中使用您的对象时,您必须同时覆盖equals()
和hashCode()
。
Java 提出了一个规则:
“如果两个对象使用 Object 类的 equals 方法相等,那么 hashcode 方法应该为这两个对象提供相同的值。”
所以,如果在我们的类中我们重写equals()
我们应该重写hashcode()
方法也遵循这个规则。equals()
和两种方法hashcode()
都用于Hashtable
,例如,将值存储为键值对。如果我们覆盖一个而不是另一个,Hashtable
如果我们使用这样的对象作为键,则可能无法按我们的意愿工作。
因为如果您不覆盖它们,您将使用 Object 中的默认实现。
鉴于实例相等和 hascode 值通常需要了解构成对象的内容,它们通常需要在您的类中重新定义以具有任何实际意义。
为了使用我们自己的类对象作为 HashMap、Hashtable 等集合中的键,我们应该通过了解集合的内部工作来覆盖这两个方法(hashCode() 和 equals())。否则,它会导致我们不期望的错误结果。
添加到@Lombo 的答案
您什么时候需要覆盖 equals() ?
Object 的 equals() 的默认实现是
public boolean equals(Object obj) {
return (this == obj);
}
这意味着只有当两个对象具有相同的内存地址时才会被认为是相等的,只有当您将对象与自身进行比较时才会如此。
但是,如果两个对象的一个或多个属性具有相同的值,您可能希望将它们视为相同(请参阅@Lombo 的答案中给出的示例)。
所以你会在这些情况下覆盖equals()
,你会给出自己的平等条件。
我已经成功实现了 equals() 并且效果很好。那么为什么他们也要求覆盖 hashCode() 呢?
好吧。只要您不在用户定义的类上使用基于“哈希”的集合,就可以了。但是在将来的某个时候,您可能想要使用HashMap
,或者HashSet
如果您不使用override
并且“正确实现” hashCode(),这些基于 Hash 的集合将无法按预期工作。
仅覆盖等于(除了@Lombo 的答案)
myMap.put(first,someValue)
myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?
首先,HashMap 检查 hashCodesecond
是否与first
. 只有当值相同时,它才会继续检查同一个桶中的相等性。
但是这里这两个对象的 hashCode 是不同的(因为它们具有不同的内存地址 - 来自默认实现)。因此,它甚至不关心检查是否相等。
如果您在覆盖的 equals() 方法中有一个断点,那么如果它们具有不同的 hashCode,它就不会介入。
contains()
检查hashCode()
并且仅当它们相同时才会调用您的equals()
方法。
为什么我们不能让 HashMap 检查所有桶中的相等性?所以我没有必要重写 hashCode() !
那么您就错过了基于哈希的集合的要点。考虑以下 :
Your hashCode() implementation : intObject%9.
以下是以桶的形式存储的密钥。
Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...
比如说,你想知道地图是否包含键 10。你想搜索所有的桶吗?还是您只想搜索一个存储桶?
根据 hashCode,您将确定如果存在 10,则它必须存在于 Bucket 1 中。因此只会搜索 Bucket 1!
它在使用值对象时很有用。以下是波特兰模式存储库的摘录:
值对象的例子有数字、日期、货币和字符串。通常,它们是被广泛使用的小物体。他们的身份基于他们的状态而不是他们的对象身份。这样,您可以拥有同一个概念值对象的多个副本。
因此,我可以拥有代表 1998 年 1 月 16 日的对象的多个副本。这些副本中的任何一个都将彼此相等。对于像这样的小对象,通常更容易创建新对象并移动它们,而不是依赖单个对象来表示日期。
值对象应始终覆盖 Java 中的 .equals()(或 Smalltalk 中的 =)。(记住也要覆盖 .hashCode() 。)
class A {
int i;
// Hashing Algorithm
if even number return 0 else return 1
// Equals Algorithm,
if i = this.i return true else false
}
hashCode()
用于确定存储桶并使用equals()
方法查找该值是否已存在于存储桶中。如果不是,它将添加,否则将替换为当前值hashCode()
来找Entry(桶),然后
equals()
找Entry里面的值如果两者都被覆盖,
地图<一>
Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...
如果等于没有被覆盖
地图<一>
Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..
如果 hashCode 没有被覆盖
地图<一>
Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...
HashCode 等价合约
1)常见错误如下例所示。
public class Car {
private String color;
public Car(String color) {
this.color = color;
}
public boolean equals(Object obj) {
if(obj==null) return false;
if (!(obj instanceof Car))
return false;
if (obj == this)
return true;
return this.color.equals(((Car) obj).color);
}
public static void main(String[] args) {
Car a1 = new Car("green");
Car a2 = new Car("red");
//hashMap stores Car type and its quantity
HashMap<Car, Integer> m = new HashMap<Car, Integer>();
m.put(a1, 10);
m.put(a2, 20);
System.out.println(m.get(new Car("green")));
}
}
没有找到绿色汽车
2. hashCode()引起的问题
该问题是由未覆盖的方法引起的hashCode()
。equals()
和之间的合同hashCode()
是:
如果两个对象具有相同的哈希码,它们可能相等也可能不相等。
public int hashCode(){
return this.color.hashCode();
}
假设您有聚合两个其他 (B) (C) 的类 (A),并且您需要将 (A) 的实例存储在哈希表中。默认实现仅允许区分实例,但不允许区分 (B) 和 (C)。所以 A 的两个实例可能相等,但默认不允许您以正确的方式比较它们。
我正在研究解释“如果你只覆盖hashCode,那么当你调用myMap.put(first,someValue)
它时,首先计算它的hashCode并将其存储在给定的桶中。然后当你调用myMap.put(first,someOtherValue)
它时,应该根据地图文档将它替换为第二个,因为它们是相等的(根据我们的定义)。” :
我认为我们第二次添加时myMap
应该是“第二个”对象myMap.put(second,someOtherValue)
考虑在桶中收集所有黑色的球。你的工作是给这些球上色如下,并将其用于适当的游戏,
网球 - 黄色,红色。板球 - 白色
现在水桶有黄色、红色和白色三种颜色的球。现在你做了着色只有你知道哪种颜色适用于哪个游戏。
给球上色 - 散列。为比赛选择球 - 等于。
如果你做了着色并且有人选择板球或网球的球,他们不会介意颜色!
方法 equals 和 hashcode 在对象类中定义。默认情况下,如果equals方法返回true,那么系统会进一步检查哈希码的值。如果 2 个对象的哈希码也相同,则对象将被视为相同。因此,如果您仅覆盖 equals 方法,那么即使覆盖的 equals 方法指示 2 个对象相等,系统定义的哈希码也可能不会指示 2 个对象相等。所以我们也需要覆盖哈希码。
Java中的Equals和Hashcode方法
它们是 java.lang.Object 类的方法,该类是所有类的超类(自定义类以及 java API 中定义的其他类)。
执行:
公共布尔等于(对象 obj)
公共 int hashCode()
公共布尔等于(对象 obj)
此方法仅检查两个对象引用 x 和 y 是否引用同一个对象。即它检查x == y。
它是自反的:对于任何参考值 x,x.equals(x) 应该返回 true。
它是对称的:对于任何参考值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应该返回 true。
它是可传递的:对于任何参考值 x、y 和 z,如果 x.equals(y) 返回 true 并且 y.equals(z) 返回 true,则 x.equals(z) 应该返回 true。
它是一致的:对于任何参考值 x 和 y,x.equals(y) 的多次调用始终返回 true 或始终返回 false,前提是没有修改对象上 equals 比较中使用的信息。
对于任何非空引用值 x,x.equals(null) 应该返回 false。
公共 int hashCode()
此方法返回调用此方法的对象的哈希码值。此方法以整数形式返回哈希码值,并支持基于哈希的集合类,如 Hashtable、HashMap、HashSet 等。必须在每个覆盖 equals 方法的类中覆盖此方法。
hashCode 的一般合约是:
每当在 Java 应用程序执行期间对同一个对象多次调用它时,hashCode 方法必须始终返回相同的整数,前提是没有修改对象上的 equals 比较中使用的信息。
该整数不需要从应用程序的一次执行到同一应用程序的另一次执行保持一致。
如果两个对象根据 equals(Object) 方法相等,则对两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
如果根据 equals(java.lang.Object) 方法,如果两个对象不相等,则不需要对两个对象中的每一个调用 hashCode 方法都必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
只要它们相等,相等的对象就必须产生相同的哈希码,但是不相等的对象不需要产生不同的哈希码。
资源:
如果您覆盖equals()
而不是覆盖hashcode()
,您将不会发现任何问题,除非您或其他人在散列集合中使用该类类型,例如HashSet
. 在我之前的人已经多次清楚地解释了文档化的理论,我只是在这里提供一个非常简单的例子。
考虑一个equals()
需要定制一些东西的类:-
public class Rishav {
private String rshv;
public Rishav(String rshv) {
this.rshv = rshv;
}
/**
* @return the rshv
*/
public String getRshv() {
return rshv;
}
/**
* @param rshv the rshv to set
*/
public void setRshv(String rshv) {
this.rshv = rshv;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Rishav) {
obj = (Rishav) obj;
if (this.rshv.equals(((Rishav) obj).getRshv())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
@Override
public int hashCode() {
return rshv.hashCode();
}
}
现在考虑这个主要课程:-
import java.util.HashSet;
import java.util.Set;
public class TestRishav {
public static void main(String[] args) {
Rishav rA = new Rishav("rishav");
Rishav rB = new Rishav("rishav");
System.out.println(rA.equals(rB));
System.out.println("-----------------------------------");
Set<Rishav> hashed = new HashSet<>();
hashed.add(rA);
System.out.println(hashed.contains(rB));
System.out.println("-----------------------------------");
hashed.add(rB);
System.out.println(hashed.size());
}
}
这将产生以下输出:-
true
-----------------------------------
true
-----------------------------------
1
我对结果很满意。但是,如果我没有覆盖hashCode()
,它将导致噩梦,因为Rishav
具有相同成员内容的对象将不再被视为唯一hashCode
,因为默认行为生成的将是不同的,这是将输出的内容:-
true
-----------------------------------
false
-----------------------------------
2
hashCode()
方法用于获取给定对象的唯一整数。这个整数用于确定桶的位置,当这个对象需要存储在一些数据结构中HashTable
时。HashMap
默认情况下,Object 的hashCode()
方法返回存储对象的内存地址的整数表示。
hashCode()
当我们将对象插入到HashTable
,HashMap
或中时使用对象的方法HashSet
。更多关于HashTables
Wikipedia.org 的信息以供参考。
要在地图数据结构中插入任何条目,我们需要键和值。如果键和值都是用户定义的数据类型,hashCode()
则键的类型将确定在内部存储对象的位置。当还需要从地图中查找对象时,键的哈希码将确定在哪里搜索对象。
哈希码仅在内部指向某个“区域”(或列表、存储桶等)。由于不同的密钥对象可能具有相同的哈希码,因此哈希码本身并不能保证找到正确的密钥。然后HashTable
迭代这个区域(所有键具有相同的哈希码)并使用键的equals()
方法找到正确的键。一旦找到正确的键,就会返回为该键存储的对象。
因此,正如我们所见,在存储和查找对象时使用hashCode()
和方法的组合。equals()
HashTable
笔记:
始终使用对象的相同属性来生成hashCode()
两者equals()
。在我们的例子中,我们使用了员工 ID。
equals()
必须是一致的(如果对象没有被修改,那么它必须保持返回相同的值)。
无论何时a.equals(b)
,则a.hashCode()
必须与 相同b.hashCode()
。
如果你覆盖一个,那么你应该覆盖另一个。
http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html
在下面的示例中,如果您在 Person 类中注释掉 equals 或 hashcode 的覆盖,此代码将无法查找 Tom 的订单。使用哈希码的默认实现可能会导致哈希表查找失败。
我在下面是一个简化的代码,可以按人提取人们的订单。Person 被用作哈希表中的键。
public class Person {
String name;
int age;
String socialSecurityNumber;
public Person(String name, int age, String socialSecurityNumber) {
this.name = name;
this.age = age;
this.socialSecurityNumber = socialSecurityNumber;
}
@Override
public boolean equals(Object p) {
//Person is same if social security number is same
if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
return true;
} else {
return false;
}
}
@Override
public int hashCode() { //I am using a hashing function in String.java instead of writing my own.
return socialSecurityNumber.hashCode();
}
}
public class Order {
String[] items;
public void insertOrder(String[] items)
{
this.items=items;
}
}
import java.util.Hashtable;
public class Main {
public static void main(String[] args) {
Person p1=new Person("Tom",32,"548-56-4412");
Person p2=new Person("Jerry",60,"456-74-4125");
Person p3=new Person("Sherry",38,"418-55-1235");
Order order1=new Order();
order1.insertOrder(new String[]{"mouse","car charger"});
Order order2=new Order();
order2.insertOrder(new String[]{"Multi vitamin"});
Order order3=new Order();
order3.insertOrder(new String[]{"handbag", "iPod"});
Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
hashtable.put(p1,order1);
hashtable.put(p2,order2);
hashtable.put(p3,order3);
//The line below will fail if Person class does not override hashCode()
Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
for(String item:tomOrder.items)
{
System.out.println(item);
}
}
}
String 类和包装类的实现equals()
和hashCode()
方法与 Object 类不同。Object 类的 equals() 方法比较对象的引用,而不是内容。Object 类的 hashCode() 方法为每个对象返回不同的哈希码,无论内容是否相同。
当您使用 Map 集合并且 key 是 Persistent 类型,StringBuffer/builder 类型时,它会导致问题。由于它们不像 String 类那样覆盖 equals() 和 hashCode(),所以当您比较两个不同的对象时,即使它们具有相同的内容,equals() 也会返回 false。它将使 hashMap 存储相同的内容键。存储相同的内容键意味着它违反了 Map 的规则,因为 Map 根本不允许重复键。因此,您在类中重写 equals() 和 hashCode() 方法并提供实现(IDE 可以生成这些方法),以便它们与 String 的 equals() 和 hashCode() 工作相同,并防止相同的内容键。
您必须与 equals() 一起覆盖 hashCode() 方法,因为 equals() 根据哈希码工作。
此外,重写 hashCode() 方法和 equals() 有助于完整地完成 equals()-hashCode() 合约:“如果两个对象相等,那么它们必须具有相同的哈希码。”
什么时候需要为 hashCode() 编写自定义实现?
众所周知,HashMap的内部工作是基于Hashing原理的。有一些存储条目集的存储桶。您可以根据需要自定义 hashCode() 实现,以便可以将相同的类别对象存储到相同的索引中。当您使用方法将值存储到 Map 集合中put(k,v)
时, put() 的内部实现是:
put(k, v){
hash(k);
index=hash & (n-1);
}
意思是,它生成索引,索引是根据特定关键对象的哈希码生成的。因此,请让此方法根据您的要求生成哈希码,因为相同的哈希码条目集将存储到相同的存储桶或索引中。
就是这样!
恕我直言,按照规则所说-如果两个对象相等,那么它们应该具有相同的哈希值,即相等的对象应该产生相等的哈希值。
如上所述,Object 中的默认 equals() 是 == 对地址进行比较,hashCode() 返回整数地址(实际地址上的哈希),这对于不同的 Object 来说也是不同的。
如果需要在基于 Hash 的集合中使用自定义 Objects,则需要同时覆盖 equals() 和 hashCode(),例如如果我想维护 Employee Objects 的 HashSet,如果我不使用更强的 hashCode 和 equals我最终可能会覆盖两个不同的员工对象,当我使用年龄作为 hashCode() 时会发生这种情况,但是我应该使用可以是员工 ID 的唯一值。
由于哈希码总是返回一个数字,因此使用数字而不是字母键检索对象总是很快。它会怎么做?假设我们通过传递一些在其他对象中已经可用的值来创建一个新对象。现在新对象将返回与另一个对象相同的哈希值,因为传递的值是相同的。一旦返回相同的哈希值,JVM 每次都会转到相同的内存地址,如果有多个对象存在相同的哈希值,它将使用 equals() 方法来识别正确的对象。
当您想在 Map 中将自定义对象作为键存储和检索时,您应该始终在自定义 Object 中覆盖 equals 和 hashCode。例如:
Person p1 = new Person("A",23);
Person p2 = new Person("A",23);
HashMap map = new HashMap();
map.put(p1,"value 1");
map.put(p2,"value 2");
这里 p1 & p2 将只考虑一个对象,并且map
大小将仅为 1,因为它们是相等的。
public class Employee {
private int empId;
private String empName;
public Employee(int empId, String empName) {
super();
this.empId = empId;
this.empName = empName;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
@Override
public String toString() {
return "Employee [empId=" + empId + ", empName=" + empName + "]";
}
@Override
public int hashCode() {
return empId + empName.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(this instanceof Employee)) {
return false;
}
Employee emp = (Employee) obj;
return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
}
}
测试班
public class Test {
public static void main(String[] args) {
Employee emp1 = new Employee(101,"Manash");
Employee emp2 = new Employee(101,"Manash");
Employee emp3 = new Employee(103,"Ranjan");
System.out.println(emp1.hashCode());
System.out.println(emp2.hashCode());
System.out.println(emp1.equals(emp2));
System.out.println(emp1.equals(emp3));
}
}
在对象类中,equals(Object obj) 用于比较地址比较,这就是为什么在测试类中,如果您比较两个对象,然后 equals 方法给出 false,但是当我们覆盖 hashcode() 时,它可以比较内容并给出正确的结果。
这两种方法都在 Object 类中定义。两者都是最简单的实现。因此,当您需要为这些方法添加更多实现时,您可以在类中覆盖。
例如:对象中的 equals() 方法仅检查其在引用上的相等性。因此,如果您还需要比较它的状态,那么您可以像在 String 类中那样覆盖它。
这个答案中没有提到测试 equals/hashcode 合约。
我发现EqualsVerifier库非常有用且全面。它也很容易使用。
此外,从头开始构建equals()
和方法涉及大量样板代码。Apache Commons LanghashCode()
库提供了EqualsBuilder和HashCodeBuilder类。这些类极大地简化了复杂类的实现和方法。equals()
hashCode()
顺便说一句,值得考虑重写该toString()
方法以帮助调试。Apache Commons Lang库提供了ToStringBuilder类来帮助解决这个问题。
呸 - “您必须在每个覆盖 equals() 的类中覆盖 hashCode()。”
[来自《有效的 Java》,作者:Joshua Bloch?]
这不是走错路了吗?覆盖 hashCode 可能意味着您正在编写一个哈希键类,但覆盖 equals 肯定不会。有许多类不用作散列键,但出于某些其他原因确实需要一种逻辑相等测试方法。如果您为它选择“等于”,那么您可能会被要求通过过度使用此规则来编写 hashCode 实现。所做的只是在代码库中添加未经测试的代码,这是一个等待将来绊倒某人的邪恶。编写不需要的代码也是反敏捷的。这是错误的(ide 生成的可能与您手工制作的 equals 不兼容)。
当然,他们应该在编写为用作键的对象上规定一个接口?无论如何,Object 永远不应该提供默认的 hashCode() 和 equals() 恕我直言。它可能鼓励了许多损坏的哈希集合。
但无论如何,我认为“规则”是从头到尾写的。同时,我将继续避免使用“equals”进行相等测试方法:-(