HashSet содержит проблему с пользовательскими объектами
Мой пользовательский класс, который будет содержаться в HashSet
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"hashcode='" + this.hashCode() + '\'' +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (age != person.age) return false;
if (!name.equals(person.name)) return false;
return true;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
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;
}
}
Тест My HashSet, который не работает
public void hashSetTest() {
Set<Person> personSet = new HashSet<Person>();
Person p1 = new Person("raghu", 12);
Person p2 = new Person("rimmu", 21);
personSet.add(p1);
personSet.add(p2);
p1.setName("raghus");
p1.setAge(13);
int i2 =p1.hashCode();
System.out.println(personSet.size() + ": "+ p1.hashCode()+" : "+personSet.contains(p1)+ " : "+i2);
}
Я ожидаю, что personSet.contains(p1) пройдет. Почему он возвращает ложь?
благодаря
шри
Ответы
Ответ 1
Потому что p1.hashCode()
изменяется при изменении p1
, поэтому его больше не можно найти в его исходном индексе в хеш-таблице. Никогда не позволяйте хеш-значению зависать от изменяемого поля.
(Вам очень повезло, что он терпит неудачу во время тестирования, он мог бы так же успешно преуспеть, только для отказа в производстве.)
Ответ 2
HashSet реализует Set. ApiDoc указывает:
Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
В вашем примере это так, потому что изменение name
или age
на p1
влияет на равное сравнение. Поэтому, согласно ApiDoc, поведение Set в вашем случае не указано.
Ответ 3
Хэши - это простое соединение ключей и значений. Здесь, как будет выглядеть состояние вашего кода до и после переименования в псевдокоде:
До:
personSet => {
SOME_NUM1 => Person(name=>"raghu", 12),
SOME_NUM2 => Person(name=>"rimmu", 21)
}
p1.setName("raghus"); #p1.hashcode() = SOME_NEW_NUM
p1.setAge(13);#p1.hashcode() = SOME_OTHER_NEW_NUM
После того, как:
personSet => {
SOME_NUM1 => Person(name=>"raghu", 13),
SOME_NUM2 => Person(name=>"rimmu", 21)
}
Поскольку у вас есть прямой доступ к p1, объект внутри HashSet обновляется правильно, но HashSet не обращает внимания на обновленные хэш-коды содержащихся объектов. Когда вызывается вызов personSet.contains(p1)
, HashSet ищет запись с новым значением p1.hashcode()
.
Объект p1
связан с его предыдущим хэш-кодом в момент его добавления в HashSet.
Ответ 4
Я думаю, вам нужно, чтобы hashCode зависел от изменяемых полей довольно часто: когда вы переопределяете равные, которые зависят от изменяемых полей.
Из контракта hashCode: "Если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объектов должен приводить к одному и тому же целочисленному результату".
Итак, если вы создадите два объекта, для которых A.equals(B) истинно, а затем измените A таким образом, чтобы вы получили A.equals(B), стало ложным, вам также необходимо изменить hashCodes.
Верно, что в документации hashCode указано, что "Не требуется, чтобы, если два объекта неравны в соответствии с методом equals (java.lang.Object), то вызов метода hashCode на каждом из двух объектов должен производить различные целочисленные результаты.", но я не знаю, как это может помочь.
Ответ 5
Вы должны переопределить hasCode и равный метод для его удаления. вы можете сделать это следующим образом
class Price{
private String item;
private int price;
public Price(String itm, int pr){
this.item = itm;
this.price = pr;
}
public int hashCode(){
System.out.println("In hashcode");
int hashcode = 0;
hashcode = price*20;
hashcode += item.hashCode();
return hashcode;
}
public boolean equals(Object obj){
System.out.println("In equals");
if (obj instanceof Price) {
Price pp = (Price) obj;
return (pp.item.equals(this.item) && pp.price == this.price);
} else {
return false;
}
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String toString(){
return "item: "+item+" price: "+price;
}
}