Метод hashCode(), когда equals() основан на нескольких независимых полях
Вопрос
у меня есть класс, равенство которого основано на 2 полях, так что если любое из них равно, то объекты этого типа считаются равными.как я могу написать функцию hashCode() для такого equals(), чтобы общий договор о том, что хэш-код равен, когда equals возвращает true, был сохранен?
public class MyClass {
int id;
String name;
public boolean equals(Object o) {
if (!(o instanceof MyClass))
return false;
MyClass other = (MyClass) o;
if (other.id == this.id || other.name == this.name)
return true;
return false;
}
}
как мне написать функцию hashCode() для этого класса?и я хочу избежать здесь тривиального случая возврата константы следующим образом:
public int hashCode() {
return 1;
}
Решение
Я не думаю, что существует нетривиальный хэш-код.Кроме того, ваш equals()
нарушает генеральный контракт, поскольку заявлено в API --- это так не транзитивный:
(1,2)
равно (1,3)
(4,3)
равно (1,3)
Но (4,3)
является не равны Для (1,2)
.
Для полноты картины я представляю вам Тарелочки-Нико доказательство =)
Претензия: Хэш-код должен быть тривиальной постоянной функцией.
Доказательство:Пусть (a,b)
и (c,d)
быть двумя объектами с различными хэш-кодами, т. е. h(a,b) ≠ h(c,d)
.Рассмотрим объект (a,d)
.По определению OP, (a,d)
равно (a,b)
, и (a,d)
равно (c,d)
.Это следует из контракт с хэш - кодом это h(a,d) = h(a,b) = h(c,d)
;противоречие.
Другие советы
Хорошо, в вашем сценарии, игнорирующем требования API на секунду, нет непостоянной хэш-функции
Представьте, что существует хэш-функция, которая имеет разные значения для
(a,b), (a,c), b!=c, тогда хэш(a,b) != хэш(a,c), хотя (a,b) = (a,c).
Аналогично, (b,a) и (c,a) должны выдавать один и тот же хэш-код.
Давайте вызовем нашу хэш-функцию h.Мы находим:
h (x, y) = h (x, w) = h (v, w) для всех x, y, v, w.
Следовательно, единственная хэш-функция, которая делает то, что вы хотите, является постоянной.
Я почти уверен, что Зак прав - для этого нет нетривиального хэш-кода.
Псевдо-доказательство:
Рассмотрим любые два неравных значения, X=(id1, name1) и Y=(id2, name2).
Теперь рассмотрим Z=(id2,name1).Это равно как X, так и Y, поэтому должно иметь тот же хэш-код, что и X и Y.Следовательно, X и Y должны иметь один и тот же хэш - код, что означает ВСЕ значения должны иметь один и тот же хэш-код.
Есть причина, по которой вы попали в странную ситуацию - вы нарушаете переходную природу equals.Тот факт, что X равно (Z), а Z.равно(Y) следует означает, что X.равно (Y) - но это не так.Ваше определение равенства не подходит для обычного договора равных.
Я думаю, ты не можешь.Причина в том, что ваш equals()
метод не является транзитивным.
Транзитивность означает для трех ненулевых x, y, z, если x.equals(y)
, y.equals(z)
, тогда x.equals(z)
.В вашем примере объект x={id: 1, name: "ha"}
, y={id: 1, name: "foo"}
, z={id: 2, name: "bar"}
обладайте этим свойством (x.equals(y) and y.equals(z))
.Однако, x.equals(z)
является false
.Каждый equals()
метод должен обладать этим свойством, смотрите Документы Java API.
Вернемся к функциям хеширования:Каждая функция дает эквивалентность , определяемую f(x)==f(y)
.Это означает, что если вы заинтересованы в сравнении значений функции и хотите, чтобы она возвращала true, если x==y
(и, возможно, в других случаях), вы получите транзитивное отношение, что означает, что вы должны рассмотреть, по крайней мере, транзитивное замыкание эквивалентности объектов.В вашем случае транзитивное замыкание - это тривиальное отношение (все равно чему угодно).Это означает, что вы не можете различать разные объекты с помощью какой-либо функции.
Вы намеренно определили равенство как случай, когда идентификаторы равны ИЛИ имена равны?.Разве "ИЛИ" не должно быть "И" ?
Если вы имели в виду "И", то ваш хэш-код должен быть рассчитан с использованием тех же самых или меньших (но никогда не используйте поля, не используемые equals) полей, что и equals() .
Если вы имели в виду "ИЛИ", то ваш r hashgcode не должен включать идентификатор или имя в свое вычисление hashcode, что на самом деле не имеет смысла.
Редактировать:Я невнимательно прочитал вопрос.
--
Я буду использовать commons-lang jar.
XOR хэш-код участников должен работать.Как и следовало ожидать, корректно реализует hashCode() и equals().
Однако ваш код может быть неправильным, если вы не защитите свой хэш-код.После того, как он был хэширован, его не следует изменять.Это должно быть предотвращено от того, чтобы это произошло.
public hashCode(){
return new AssertionError();
}
или
public class MyClass {
final int id;
final String name;
// constructor
}
или
public class MyClass {
private int id;
private String name;
boolean hashed=false;
public void setId(int value){
if(hashed)throw new IllegalStateException();
this.id=value;
}
public void setName(String value){
if(hashed)throw new IllegalStateException();
this.name=value;
}
// your equals() here
public hashCode(){
hashed=true;
return new HashCodeBuilder().append(id).append(name).toHashCode();
}
}
После перечитайте вопрос.
Вы можете автоматически заполнить другое поле, когда одно из них обновляется.
--
Редактировать:Мой код может говорить лучше, чем мой английский.
void setName(String value){
this.id=Lookup.IDbyName(value);
}
void setID(String value){
this.name=Lookup.NamebyId(value);
}
ПРАВКА 2:
Код в вопросе может быть неправильным, так как всегда будет возвращать значение true, если вы не задали как идентификатор, так и имя.
Если вам действительно нужен метод, который выполняет частичное равенство, создайте свой собственный API с именем "partialEquals()".
Самый простой способ - выполнить XOR для хэш-кодов каждого отдельного поля.Это имеет незначительное уродство в некоторых ситуациях (напримерв координатах X, Y это приводит к потенциально плохой ситуации с равными хэшами при переворачивании X и Y), но в целом довольно эффективно.Настраивайте по мере необходимости, чтобы уменьшить коллизии, если это необходимо для повышения эффективности.
Как насчет этого
public override int GetHashCode()
{
return (id.ToString() + name.ToString()).GetHashCode();
}
Функция всегда должна возвращать "действительный" хэш...
Редактировать:только что заметил, что вы используете "или", а не "и" :P ну, я сомневаюсь, что есть какое-либо хорошее решение этой проблемы...
Как насчет
public override int GetHashCode()
{
return id.GetHashCode() ^ name.GetHashCode();
}