Метод hashCode (), когда метод equals () основан на нескольких независимых полях - PullRequest
9 голосов
/ 26 января 2009

У меня есть класс, равенство которого основано на 2 полях, так что если любое из них равно, объекты этого типа считаются равными. Как я могу написать функцию hashCode () для такого equals (), чтобы общий контракт hashCode, равный, когда 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;
}

Ответы [ 10 ]

22 голосов
/ 26 января 2009

Я не думаю, что существует нетривиальный хэш-код. Кроме того, ваш equals() нарушает общий контракт как , указанный в API --- это не переходный :

(1,2) равно (1,3)

(4,3) равно (1,3)

Но (4,3) не равно не равно (1,2).


Ради полноты я представляю вам Skeet - Niko proof =)

Заявка : Хеш-код должен быть тривиальной постоянной функцией.

Доказательство : пусть (a,b) и (c,d) будут двумя объектами с разными хэш-кодами, т.е. h(a,b) ≠ h(c,d). Рассмотрим объект (a,d). По определению ФП, (a,d) равно (a,b), а (a,d) равно (c,d). Из хеш-кода следует, что h(a,d) = h(a,b) = h(c,d); противоречие.

8 голосов
/ 26 января 2009

Хорошо, в вашем сценарии, игнорируя требования API на секунду, не существует непостоянной хэш-функции

Представьте, что существует хэш-функция, которая имеет различные значения для

(a, b), (a, c), b! = C, затем hash (a, b)! = Hash (a, c), даже если (a, b) = (a, c).

Аналогично, (b, a) и (c, a) должны выдавать один и тот же хэш-код.

Давайте назовем нашу хеш-функцию h. Мы находим:

h (x, y) = h (x, w) = h (v, w) по всем x, y, v, w.

Следовательно, единственная хэш-функция, которая делает то, что вы хотите, является константой.

6 голосов
/ 26 января 2009

Я почти уверен, что Зак прав - для этого нет нетривиального хеш-кода.

Псевдо-доказательство:

Рассмотрим любые два неравных значения: X = (id1, name1) и Y = (id2, name2).

Теперь рассмотрим Z = (id2, name1). Это равно и X, и Y, поэтому должен иметь одинаковый хэш-код, как и X, так и Y. Поэтому X и Y должны иметь одинаковый хеш-код - это означает, что все значения должны иметь одинаковый хеш-код.

Есть причина, по которой вы попали в странную ситуацию - вы нарушаете переходную природу равных. Тот факт, что X.equals (Z) и Z.equals (Y) должны , означает, что X.equals (Y) - но это не так. Ваше определение равенства не подходит для обычного контракта равных.

2 голосов
/ 26 января 2009

Вы преднамеренно определили равенство, когда идентификаторы равны ИЛИ имена равны? Разве «ИЛИ» не должно быть «И»?

Если вы имели в виду «И», тогда ваш хеш-код должен быть рассчитан с использованием тех же самых или меньших (но никогда не используйте полей, не используемых равными) полей, которые у вас равны ().

Если вы имели в виду «ИЛИ», тогда ваш хеш-код не должен включать идентификатор или имя в вычисление хеш-кода, что на самом деле не имеет смысла.

2 голосов
/ 26 января 2009

Я думаю, что вы не можете. Причина в том, что ваш 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() должен иметь это свойство, см. Документацию по API Java.

Возвращение к функциям хеширования. Каждая функция дает эквивалентность, определенную f(x)==f(y). Это означает, что если вы заинтересованы в сравнении значений функции и хотите, чтобы она возвращала значение true, если x==y (и, возможно, в других случаях), вы получите транзитивное отношение, что означает, что вы должны рассмотреть как минимум транзитивное замыкание эквивалентность объектов. В вашем случае транзитивное замыкание является тривиальным отношением (все равно чему-либо). Это означает, что вы не можете различить различные объекты по любой функции.

0 голосов
/ 26 января 2009

После перечитайте вопрос.

Вы можете автоматически заполнять другое поле при обновлении одного из них.

-

РЕДАКТИРОВАТЬ: мой код может сказать лучше, чем мой английский.

void setName(String value){
  this.id=Lookup.IDbyName(value);
}
void setID(String value){
  this.name=Lookup.NamebyId(value);
}

РЕДАКТИРОВАТЬ 2:

Код в вопросе может быть неправильным, так как всегда будет возвращать true, если вы не указали ни идентификатор, ни имя.

Если вам действительно нужен метод, который выполняет частичное равенство, создайте свой собственный API с именемpartalEquals ().

0 голосов
/ 26 января 2009

РЕДАКТИРОВАТЬ: я не внимательно прочитал вопрос.

-

Я буду использовать банку commons-lang.

XOR члены hashCode должны работать. Как они должны правильно реализовать 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();
   }
}
0 голосов
/ 26 января 2009

Как насчет этого

public override int GetHashCode()
{
    return (id.ToString() + name.ToString()).GetHashCode();
}

Функция всегда должна возвращать "действительный" хеш ...

Редактировать: только что заметил, что вы используете "или" не "и": P ну, я сомневаюсь, что есть какое-то хорошее решение этой проблемы ...

0 голосов
/ 26 января 2009

Как насчет

public override int GetHashCode()
{
    return id.GetHashCode() ^ name.GetHashCode();
}
0 голосов
/ 26 января 2009

Самый простой маршрут - это XOR хеш-кодов каждого отдельного поля. Это имеет незначительное уродство в некоторых ситуациях (например, в координатах X, Y, это вызывает потенциально плохую ситуацию с равными хэш-значениями, когда вы переворачиваете X и Y), но в целом довольно эффективно. Настроить по мере необходимости, чтобы уменьшить столкновения, если это необходимо для эффективности.

...