Метод hashCode(), когда equals() основан на нескольких независимых полях

StackOverflow https://stackoverflow.com/questions/479105

  •  20-08-2019
  •  | 
  •  

Вопрос

у меня есть класс, равенство которого основано на 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();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top