Почему мне нужно переопределить методы equals и hashCode в Java?
Недавно я прочитал этот документ Developer Works.
Документ все об определении hashCode()
а также equals()
эффективно и правильно, однако я не могу понять, почему мы должны переопределить эти два метода.
Как я могу принять решение для эффективной реализации этих методов?
33 ответа
Джошуа Блох говорит об эффективной Java
Вы должны переопределить hashCode() в каждом классе, который переопределяет equals(). Невыполнение этого требования приведет к нарушению общего контракта для Object.hashCode(), что помешает правильной работе вашего класса в сочетании со всеми коллекциями на основе хешей, включая HashMap, HashSet и Hashtable.
Давайте попробуем понять это на примере того, что произойдет, если мы переопределим equals()
без переопределения hashCode()
и попытаться использовать Map
,
Скажем, у нас есть такой класс, и что два объекта MyClass
равны, если их importantField
равно (с hashCode()
а также equals()
генерируется затмением)
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;
}
public String getEqualField() {
return importantField;
}
public String getAnotherField() {
return 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;
}
}
Только переопределитьequals
Если только equals
переопределяется, то при вызове myMap.put(first,someValue)
сначала будет хеш к некоторому ведру, и когда вы звоните myMap.put(second,someOtherValue)
он будет хэшировать в другое ведро (так как у них другое hashCode
). Таким образом, хотя они равны, так как они не хешируют одно и то же ведро, карта не может этого понять, и они оба остаются на карте.
Хотя не обязательно переопределять equals()
если мы переопределим hashCode()
давайте посмотрим, что произойдет в этом конкретном случае, когда мы знаем, что два объекта MyClass
равны, если их importantField
равно, но мы не переопределяем equals()
,
Только переопределитьhashCode
Представь, что у тебя есть это
MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");
Если вы только переопределите 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
возражать в HashSet
и проверьте, присутствует ли этот объект или нет.
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
Теперь вы можете понять, почему, если два объекта считаются равными, их хэш-коды также должны быть равны? В противном случае вы никогда не сможете найти объект, поскольку методхеш-кода по умолчанию в классе Object практически всегда предлагает уникальный номер для каждого объекта, даже если equals()
Метод переопределяется таким образом, что два или более объектов считаются равными. Не имеет значения, насколько равны объекты, если их хэш-коды не отражают это. Итак, еще раз: если два объекта равны, иххэш-код s также должен быть равен.
Вы должны переопределить hashCode() в каждом классе, который переопределяет equals(). Невыполнение этого требования приведет к нарушению общего контракта для Object.hashCode(), что помешает правильной работе вашего класса в сочетании со всеми коллекциями на основе хешей, включая HashMap, HashSet и Hashtable.
из Эффективной Явы, Джошуа Блох
Определяя equals()
а также hashCode()
последовательно вы можете улучшить удобство использования ваших классов в качестве ключей в основанных на хэше коллекциях. Как объясняет документ API для hashCode: "Этот метод поддерживается для использования хеш-таблиц, таких как те, которые предоставляются java.util.Hashtable
".
Лучший ответ на ваш вопрос о том, как эффективно реализовать эти методы, - это предложить вам прочитать главу 3 " Эффективная Java".
Почему мы переопределяем метод
В java мы не можем перегружать поведение операторов вроде ==, + =, - +. Они ведут себя определенным образом. Итак, давайте сосредоточимся на операторе == для нашего случая.
Как работает оператор ==. Он проверяет, указывают ли 2 ссылки, которые мы сравниваем, на один и тот же экземпляр в памяти. Это будет верно, только если эти 2 ссылки представляют один и тот же экземпляр в памяти.
Итак, теперь давайте рассмотрим следующий пример
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, что мы хотим, чтобы эти 2 объекта обрабатывались одинаково. В игру вступает метод.
Вы можете переопределить, чтобы проверить, имеют ли некоторые объекты одинаковые значения, чтобы определенные поля считались равными.
Вы можете выбрать, какие поля вы хотите сравнивать. Если мы скажем, что 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() из класса Object и оператор == будут вести себя точно так же. Метод по умолчанию, унаследованный от Object, проверит, совпадают ли оба сравниваемых экземпляра в памяти!
Почему мы переопределяем метод
Некоторые структуры данных в java, такие как HashSet, HashMap, хранят свои элементы на основе хэш-функции, которая применяется к этим элементам. Функция хеширования - это
Если у нас есть выбор переопределения
.equals()
method, то у нас также должен быть выбор метода переопределения. Для этого есть причина.
Реализация по умолчанию, унаследованная от Object, считает все объекты в памяти уникальными!
Вернемся к этим структурам хеш-данных. Для этих структур данных есть правило.
HashSet не может содержать повторяющиеся значения, а HashMap не может содержать повторяющиеся ключи.
HashSet реализуется с помощью HashMap за кулисами, где каждое значение HashSet хранится как ключ в HashMap.
Итак, мы должны понять, как работает HashMap.
Проще говоря, HashMap - это собственный массив, в котором есть несколько сегментов. У каждой корзины есть связанный список. В этом связанном списке хранятся наши ключи. HashMap находит правильный связанный список для каждого ключа, применяя метод, и после этого он перебирает все элементы этого связанного списка и применяет метод к каждому из этих элементов, чтобы проверить, содержится ли там уже этот элемент. Дублирующие ключи не допускаются.
Когда мы помещаем что-то в HashMap, ключ сохраняется в одном из этих связанных списков. В каком связанном списке будет храниться этот ключ, показывает результат метода для этого ключа. Так что если
key1.hashCode()
имеет в результате 4, то этот ключ1 будет сохранен в 4-м сегменте массива в связанном списке, который существует там .
По умолчанию метод возвращает разные результаты для каждого экземпляра. Если у нас есть значение по умолчанию, которое ведет себя как ==, который рассматривает все экземпляры в памяти как разные объекты, у нас нет никаких проблем.
Но в нашем предыдущем примере мы сказали, что хотим, чтобы экземпляры Person считались равными, если их возраст и имена совпадают.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
Теперь давайте создадим карту для хранения этих экземпляров в виде ключей с некоторой строкой в виде пары значений.
В классе Person мы не переопределили метод, но у нас есть переопределенный метод. Поскольку по умолчанию
hashCode
предоставляет разные результаты для разных экземпляров java
person1.hashCode()
а также
person2.hashCode()
имеют большие шансы на разные результаты.
Наша карта может заканчиваться этими людьми в разных связанных списках.
Это противоречит логике HashMap
HashMap не может иметь несколько одинаковых ключей !!!
Но у нас теперь есть, и причина в том, что значения по умолчанию, унаследованного от класса объекта, было недостаточно. Не после того, как мы переопределили метод в классе Person.
Вот почему мы должны переопределить метод после того, как переопределили
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, чтобы сохранить ключ person2 со значением «2», он увидит, что там уже существует другой равный ключ. Таким образом, он перезапишет предыдущий ключ. Таким образом, в конечном итоге в нашей HashMap будет существовать только ключ person2.
Теперь мы согласны с правилом HashMap, согласно которому нельзя использовать несколько одинаковых ключей !!!
Личность - это не равенство.
- оператор равенства
==
проверить личность. equals(Object obj)
метод сравнивает тест на равенство (т. е. нам нужно определить равенство, переопределив метод)
Почему мне нужно переопределить методы equals и hashCode в Java?
Сначала мы должны понять использование метода равных.
Чтобы идентифицировать различия между двумя объектами, нам нужно переопределить метод 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
как указано выше,
customer1.equals(customer2); // returns true by our own logic
При работе со структурой данных, когда мы храним объект в контейнерах (bucket - причудливое имя для папки). Если мы используем встроенную технику хеширования, для более чем двух клиентов она генерирует два разных хеш-кода. Таким образом, мы храним один и тот же объект в двух разных местах. Чтобы избежать подобных проблем, мы должны переопределить метод hashCode, также основанный на следующих принципах.
- неравные экземпляры могут иметь одинаковый хэш-код.
- равные экземпляры должны возвращать тот же хеш-код.
Проще говоря, метод equals в Object проверяет равенство ссылок, когда два экземпляра вашего класса могут быть семантически равными, когда свойства равны. Это, например, важно, когда вы помещаете ваши объекты в контейнер, который использует equals и hashcode, такие как HashMap и Set. Допустим, у нас есть такой класс:
public class Foo {
String id;
String whatevs;
Foo(String id, String whatevs) {
this.id = id;
this.whatevs = whatevs;
}
}
Мы создаем два экземпляра с одинаковым идентификатором:
Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");
Без переопределения равных мы получаем:
- a.equals(b) ложно, потому что это два разных случая
- a.equals(a) верно, так как это тот же экземпляр
- b.equals(b) верно, так как это тот же экземпляр
Правильный? Ну, может быть, если это то, что вы хотите. Но скажем, мы хотим, чтобы объекты с одинаковым идентификатором были одним и тем же объектом, независимо от того, являются ли они двумя разными экземплярами. Мы переопределяем равенства (и хэш-код):
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.
Хорошо, позвольте мне объяснить концепцию очень простыми словами.
Во-первых, в более широком плане у нас есть коллекции, и hashmap является одной из структур данных в коллекциях.
Чтобы понять, почему мы должны переопределить оба метода equals и hashcode, нужно сначала понять, что такое hashmap и что делает.
Хэш-карта - это структура данных, которая хранит пары ключевых значений данных в виде массива. Скажем, [], где каждый элемент в 'a' является парой ключ-значение.
Также каждый индекс в вышеуказанном массиве может быть связанным списком, таким образом, имея более одного значения в одном индексе.
Теперь, почему используется hashmap? Если нам нужно искать среди большого массива, тогда поиск по каждому из них, если они не будут эффективными, так что метод хэша говорит нам, что позволяет предварительно обрабатывать массив с некоторой логикой и группировать элементы на основе этой логики, т.е. хеширования
Например: у нас есть массив 1,2,3,4,5,6,7,8,9,10,11, и мы применяем хеш-функцию mod 10, поэтому 1,11 будут сгруппированы вместе. Таким образом, если бы нам пришлось искать 11 в предыдущем массиве, нам пришлось бы выполнять итерацию всего массива, но когда мы группируем его, мы ограничиваем объем итерации, тем самым повышая скорость. Эту структуру данных, используемую для хранения всей вышеупомянутой информации, для простоты можно рассматривать как двумерный массив.
Теперь помимо вышеприведенного хэш-карты также сказано, что он не будет добавлять в него никаких дубликатов. И это главная причина, почему мы должны переопределить равенства и хэш-код
Поэтому, когда говорят, что объясняют внутреннюю работу hashmap, нам нужно найти, какие методы есть у hashmap и как она следует вышеприведенным правилам, которые я объяснил выше.
поэтому в hashmap есть метод, называемый put(K,V), и согласно hashmap он должен следовать приведенным выше правилам эффективного распределения массива и не добавлять дубликаты
так что дело в том, что он сначала сгенерирует хеш-код для данного ключа, чтобы решить, в какой индекс должно входить значение. Если в этом индексе ничего нет, тогда новое значение будет добавлено туда, если что-то там уже присутствует затем новое значение должно быть добавлено после конца связанного списка в этом индексе. но помните, что дубликаты не должны добавляться в соответствии с желаемым поведением хэш-карты. Допустим, у вас есть два объекта Integer 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;
Integer 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
для каждого объекта в качестве класса объекта.
equals()
:
Если вы переопределяете только равный метод, a.equals(b)
правда, это означает, что hashCode
а и б должны быть одинаковыми, но не произойти. Потому что ты не переопределил hashCode
метод.
Замечания: hashCode()
метод класса Object всегда возвращает новый hashCode
для каждого объекта.
Поэтому, когда вам нужно использовать свой объект в коллекции на основе хеширования, необходимо переопределить оба equals()
а также hashCode()
,
Java ставит правило, которое
"Если два объекта равны при использовании метода Object класса equals, то метод хэш-кода должен дать одинаковое значение для этих двух объектов".
Итак, если в нашем классе мы переопределить equals()
мы должны переопределить hashcode()
Способ также следовать этому правилу. Оба метода, equals()
а также hashcode()
, используются в Hashtable
например, для сохранения значений в виде пар ключ-значение. Если мы переопределим одно, а не другое, есть вероятность, что Hashtable
может работать не так, как мы хотим, если мы используем такой объект в качестве ключа.
Добавление к ответу @Lombo
Когда вам нужно переопределить equals()?
Реализация по умолчанию Object's equals()
public boolean equals(Object obj) {
return (this == obj);
}
это означает, что два объекта будут считаться равными только в том случае, если они имеют один и тот же адрес памяти, что будет истинно, только если вы сравниваете объект с самим собой.
Но вы можете рассмотреть два объекта одинаково, если они имеют одинаковое значение для одного или нескольких своих свойств (см. Пример, приведенный в ответе @Lombo).
Так что вы переопределите equals()
в этих ситуациях и вы бы дали свои условия для равенства.
Я успешно реализовал equals (), и он работает отлично. Так почему же они просят переопределить hashCode ()?
Хорошо. Пока вы не используете коллекции, основанные на хеше, в своем пользовательском классе, это нормально. Но какое-то время в будущем вы можете использовать HashMap
или же HashSet
и если вы этого не сделаете override
и "правильно реализовать" hashCode (), эта коллекция на основе хэша не будет работать так, как задумано.
Переопределить только равно (дополнение к ответу @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 проверяет, является ли хэш-код second
такой же как first
, Только если значения одинаковы, он будет проверять равенство в том же сегменте.
Но здесь hashCode отличается для этих двух объектов (потому что они имеют разные адреса памяти - от реализации по умолчанию). Следовательно, он даже не будет заботиться о равенстве.
Если у вас есть точка останова внутри вашего переопределенного метода equals (), он не будет вмешиваться, если у них разные hashCodes. 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. Хотели бы вы найти все корзины? или вы хотите искать только одно ведро?
Основываясь на хэш-коде, вы должны определить, что если присутствует 10, оно должно присутствовать в сегменте 1. Поэтому будет выполняться поиск только в сегменте 1!!
Потому что, если вы не переопределите их, вы будете использовать имплантацию по умолчанию в Object.
Учитывая, что равенство экземпляров и значения hascode, как правило, требуют знания того, из чего состоит объект, они, как правило, должны быть переопределены в вашем классе, чтобы иметь какое-либо ощутимое значение.
Чтобы использовать наши собственные объекты класса в качестве ключей в коллекциях, таких как HashMap, Hashtable и т. Д., Мы должны переопределить оба метода ( hashCode() и equals()), имея представление о внутренней работе коллекции. В противном случае это приведет к неверным результатам, которых мы не ожидаем.
Это полезно при использовании объектов значения. Ниже приводится выдержка из репозитория шаблонов Portland:
Примерами объектов-значений являются такие вещи, как числа, даты, денежные суммы и строки. Обычно это небольшие объекты, которые используются довольно широко. Их идентичность основана на их состоянии, а не на их объектной идентичности. Таким образом, вы можете иметь несколько копий одного концептуального объекта значения.
Поэтому у меня может быть несколько копий объекта, представляющего дату 16 января 1998 года. Любая из этих копий будет равна друг другу. Для небольших объектов, таких как этот, часто проще создавать новые и перемещать их, чем полагаться на один объект для представления даты.
Объект значения всегда должен переопределять.equals() в Java (или = в Smalltalk). (Не забудьте переопределить.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(); }
class A {
int i;
// Hashing Algorithm
if even number return 0 else return 1
// Equals Algorithm,
if i = this.i return true else false
}
- put('key','value') вычислит значение хеша, используя
hashCode()
определить ведро и используетequals()
метод, чтобы найти, присутствует ли значение в Bucket. Если нет, то он будет добавлен, иначе он будет заменен текущим значением. - get('key') будет использовать
hashCode()
найти запись (ведро) первым и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 равный контракт
- Два ключа, равные по одинаковому методу, должны генерировать одинаковый hashCode
- Два ключа, генерирующие один и тот же хэш-код, не обязательно должны быть равными (в приведенном выше примере все четные числа генерируют один и тот же хэш-код)
Рассмотрим коллекцию шариков в ведре в черном цвете. Ваша работа состоит в том, чтобы раскрасить эти шары следующим образом и использовать их для соответствующей игры,
Для тенниса - желтый, красный. Для крикета - белый
Теперь в ведре есть шарики трех цветов: желтый, красный и белый. И что теперь вы сделали раскраску? Только вы знаете, какой цвет для какой игры.
Окраска шаров - хеширование. Выбор мяча для игры - Равно.
Если вы сделали раскраску, и кто-то выбрал мяч для игры в крикет или теннис, они не будут против цвета!!!
Предположим, у вас есть класс (A), который объединяет два других (B) (C), и вам нужно хранить экземпляры (A) внутри хеш-таблицы. Реализация по умолчанию позволяет различать только экземпляры, но не по (B) и (C). Таким образом, два экземпляра A могут быть равны, но по умолчанию не позволит вам сравнить их правильно.
Я искал объяснение "Если вы переопределяете только hashCode, то когда вы вызываете myMap.put(first,someValue)
он берет первый, вычисляет свой hashCode и сохраняет его в заданном сегменте. Затем, когда вы звоните myMap.put(first,someOtherValue)
он должен заменить первый на второй согласно документации к карте, потому что они равны (согласно нашему определению).":
Я думаю, 2-й раз, когда мы добавляем myMap
тогда это должен быть второй объект myMap.put(second,someOtherValue)
Методы equals и hashcode определены в классе объекта. По умолчанию, если метод equals возвращает true, тогда система пойдет дальше и проверит значение хеш-кода. Если хеш-код двух объектов также одинаков только тогда, объекты будут считаться одинаковыми. Таким образом, если вы переопределяете только метод equals, то даже если переопределенный метод equals указывает на 2 равных объекта, определенный хэш-кодом системы может не указывать, что эти два объекта равны. Поэтому нам нужно переопределить и хеш-код.
Методы Equals и Hashcode в Java
Это методы класса java.lang.Object, который является суперклассом всех классов (в том числе пользовательских классов и других, определенных в java API).
Реализация:
public boolean equals (Object obj)
public int hashCode ()
public boolean equals (Object obj)
Этот метод просто проверяет, относятся ли две ссылки на объекты x и y к одному и тому же объекту. т.е. проверяет, если х == у.
Это рефлексивно: для любого ссылочного значения x, x.equals(x) должен возвращать true.
Это симметрично: для любых ссылочных значений x и y x.equals(y) должен возвращать true, если и только если y.equals(x) возвращает true.
Это транзитивно: для любых ссылочных значений x, y и z, если x.equals(y) возвращает true и y.equals(z) возвращает true, тогда x.equals(z) должен возвращать true.
Это согласуется: для любых ссылочных значений x и y множественные вызовы x.equals(y) последовательно возвращают true или последовательно возвращают false при условии, что никакая информация, используемая в сравнениях сравнения на объекте, не изменяется.
Для любого ненулевого ссылочного значения x, x.equals(null) должен возвращать false.
public int hashCode ()
Этот метод возвращает значение хеш-кода для объекта, для которого этот метод вызывается. Этот метод возвращает значение хэш-кода в виде целого числа и поддерживается для использования классов коллекции на основе хеширования, таких как Hashtable, HashMap, HashSet и т. Д. Этот метод должен быть переопределен в каждом классе, который переопределяет метод equals.
Генеральный договор hashCode:
Всякий раз, когда он вызывается для одного и того же объекта более одного раза во время выполнения приложения Java, метод hashCode должен последовательно возвращать одно и то же целое число при условии, что никакая информация, используемая в сравнениях сравнения для объекта, не изменяется.
Это целое число не должно оставаться согласованным от одного выполнения приложения к другому выполнению того же приложения.
Если два объекта равны в соответствии с методом 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
Согласно документации Java, разработчики должны переопределить оба метода для достижения полностью работающего механизма равенства, и недостаточно просто реализовать метод equals().
Если два объекта равны в соответствии с методом equals(Object), то вызов метода hashcode() для каждого из двух объектов должен привести к одному и тому же целочисленному результату.
Одна только переопределенная функция equals() будет служить вашим потребностям при проверке равенства двух нормальных объектов, а также будет работать с вами при поиске элемента в списке.
Однако вы не сможете работать с такими структурами хэширования, как: HashSet, HashMap, HashTable.
Этот учебник подробно описывает наряду с примерами, почему необходимо всегда переопределять equals() и hashcode() вместе. Это стоит прочитать, проверьте это.
В приведенном ниже примере, если вы закомментируете переопределение для equals или hashcode в классе Person, этот код не сможет найти порядок Тома. Использование реализации хеш-кода по умолчанию может привести к сбоям в поисках хеш-таблиц.
Ниже приведен упрощенный код, который определяет порядок людей. 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()
методы, чем класс объекта. Метод equals() класса Object сравнивает ссылки на объекты, а не их содержимое. Метод hashCode() класса Object возвращает отдельный хэш-код для каждого отдельного объекта, независимо от того, является ли содержимое одинаковым.
Это приводит к проблемам, когда вы используете коллекцию карт, а ключ имеет тип Persistent, StringBuffer/ тип компоновщика. Поскольку они не переопределяют equals() и hashCode() в отличие от класса String, equals() вернет false, когда вы сравниваете два разных объекта, даже если оба имеют одинаковое содержимое. Это сделает хэш-карту, хранящую те же ключи содержимого. Хранение одних и тех же ключей содержимого означает, что это нарушает правило Map, поскольку Map вообще не допускает дублирование ключей. Поэтому вы переопределяете методы equals(), а также hashCode() в своем классе и предоставляете реализацию (IDE может генерировать эти методы), чтобы они работали так же, как String equals() и hashCode(), и предотвращали использование одних и тех же ключей содержимого.
Вы должны переопределить метод hashCode() вместе с equals(), потому что equals() работает в соответствии с hashcode.
Более того, переопределение метода hashCode() вместе с equals() помогает исправить контракт equals()-hashCode(): "Если два объекта равны, то они должны иметь одинаковый хэш-код".
Когда вам нужно написать собственную реализацию для hashCode()?
Как известно, внутренняя работа HashMap основана на принципе хеширования. Есть определенные сегменты, где хранятся наборы записей. Вы настраиваете реализацию hashCode() в соответствии с вашими требованиями, чтобы объекты одной категории можно было сохранить в одном индексе. когда вы сохраняете значения в коллекцию карт, используя put(k,v)
Метод, внутренняя реализация put () является:
put(k, v){
hash(k);
index=hash & (n-1);
}
Значит, он генерирует индекс, а индекс генерируется на основе хеш-кода конкретного ключевого объекта. Поэтому сделайте так, чтобы этот метод генерировал хеш-код в соответствии с вашими требованиями, потому что одни и те же наборы записей хеш-кода будут храниться в том же сегменте или индексе.
Это оно!
hashCode()
Метод используется для получения уникального целого числа для данного объекта. Это целое число используется для определения местоположения сегмента, когда этот объект должен быть сохранен в некоторых HashTable
, HashMap
как структура данных. По умолчанию объекты hashCode()
метод возвращает целочисленное представление адреса памяти, где хранится объект.
hashCode()
Метод объектов используется, когда мы вставляем их в HashTable
, HashMap
или же HashSet
, Больше о HashTables
на Wikipedia.org для справки.
Чтобы вставить любую запись в структуру данных карты, нам нужны и ключ, и значение. Если и ключ, и значения являются пользовательскими типами данных, hashCode()
ключа будет определять, где хранить объект внутри. Когда требуется поиск объекта на карте, хеш-код ключа будет определять, где искать объект.
Внутренний хеш-код указывает только на определенную "область" (или список, сегмент и т. Д.). Поскольку разные ключевые объекты могут потенциально иметь один и тот же хеш-код, сам хеш-код не является гарантией того, что правильный ключ найден. HashTable
затем перебирает эту область (все ключи с одинаковым хеш-кодом) и использует ключ equals()
метод, чтобы найти правильный ключ. Как только правильный ключ найден, объект, сохраненный для этого ключа, возвращается.
Итак, как мы видим, комбинация hashCode()
а также equals()
методы используются при хранении и при поиске объектов в HashTable
,
ЗАМЕТКИ:
Всегда используйте одни и те же атрибуты объекта для генерации
hashCode()
а такжеequals()
и то и другое. Как и в нашем случае, мы использовали идентификатор сотрудника.equals()
должен быть непротиворечивым (если объекты не изменены, то он должен продолжать возвращать одно и то же значение).Всякий раз, когда
a.equals(b)
, затемa.hashCode()
должен быть таким же, какb.hashCode()
,Если вы переопределяете одно, то вы должны переопределить другое.
http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html
Причина этого: когда ваши поля объекта могут быть нулевыми, реализация Object.equals может быть трудной задачей, потому что вы должны отдельно проверять наличие нулевых значений. Использование Objects.equal позволяет выполнять проверки на равенство с учетом нуля, не рискуя исключением NullPointerException.Objects.equal("a", "a"); // returns true
Objects.equal(null, "a"); // returns false
Objects.equal("a", null); // returns false
Objects.equal(null, null); // returns true
Чтобы помочь вам найти дубликаты Объектов, нам нужен пользовательский метод equals и hashCode.
Поскольку хэш-код всегда возвращает число, всегда можно быстро получить объект, используя номер, а не алфавитный ключ. Как это будет сделано? Предположим, мы создали новый объект, передав некоторое значение, которое уже доступно в другом объекте. Теперь новый объект будет возвращать то же хеш-значение, что и для другого объекта, поскольку переданное значение такое же. Когда возвращается одно и то же значение хеш-функции, JVM будет каждый раз переходить на один и тот же адрес памяти, и если в случае наличия более одного объекта для одного и того же хеш-значения, он будет использовать метод equals() для определения правильного объекта.
ИМХО, это согласно правилу гласит: если два объекта равны, то они должны иметь одинаковый хэш, т. Е. Равные объекты должны создавать одинаковые значения хеша.
Учитывая выше, по умолчанию equals() в Object is ==, который выполняет сравнение по адресу, hashCode() возвращает адрес в целых числах (хэш по фактическому адресу), который опять-таки различен для отдельного Object.
Если вам нужно использовать пользовательские объекты в коллекциях, основанных на Hash, вам нужно переопределить и equals(), и hashCode(), например, если я хочу сохранить HashSet объектов Employee, если я не использую более сильный hashCode и равно Я могу переопределить два разных объекта Employee, это происходит, когда я использую age в качестве hashCode(), однако я должен использовать уникальное значение, которое может быть идентификатором Employee ID.
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));
}
}
В Object Class equals(Object obj) используется для сравнения адреса, поэтому, когда в классе Test вы сравниваете два объекта, метод equals дает false, но когда мы переопределяем hashcode(), он может сравнивать содержимое и давать правильный результат.
Если вы хотите сохранить и извлечь свой пользовательский объект как ключ в Map, тогда вы должны всегда переопределять 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, потому что они равны.