Подтвердить что ты не робот

Является ли функция hashCode, сгенерированная Eclipse, хорошей?

В исходном меню Eclipse есть "генерировать метод hashCode/equals", который генерирует функции, подобные приведенным ниже.

String name; 
@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    CompanyRole other = (CompanyRole) obj;
    if (name == null)
    {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

Если я выбираю несколько полей при генерации hashCode() и equals(), Eclipse использует тот же шаблон, который показан выше.

Я не специалист по хеш-функциям, и я хотел бы знать, как "хорошая" генерируемая хэш-функция? Каковы ситуации, когда он сломается и вызовет слишком много столкновений?

4b9b3361

Ответ 1

Вы можете увидеть реализацию функции hashCode в java.util.ArrayList как

public int hashCode() {
    int hashCode = 1;
    Iterator<E> i = iterator();
    while (i.hasNext()) {
        E obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
    }
    return hashCode;
}

Это один из таких примеров, и ваш сгенерированный код Eclipse следует аналогичному способу его реализации. Но если вы чувствуете, что вам нужно реализовать свой хэш-код по своему усмотрению, есть некоторые хорошие рекомендации, данные Джошуа Блохом в его знаменитой книге Эффективная Java. Я опубликую эти важные пункты из пункта 9 этой книги. Это

  • Хранить некоторое постоянное ненулевое значение, например, 17, в переменной int, называемой результатом.
  • Для каждого значимого поля f в вашем объекте (каждое поле, принятое во внимание с помощью метода equals), выполните следующие действия:

    а. Вычислить int хэш-код c для поля:

    я. Если поле является булевым, вычислите (f? 1: 0).

    II. Если поле является байтом, char, short или int, compute (int) f.

    III. Если поле длинное, вычислите (int) (f ^ (f → > 32)).

    IV. Если поле является float, вычислите Float.floatToIntBits(f).

    v. Если поле является двойным, вычислите Double.doubleToLongBits(f), а затем хеш, как результат на этапе 2.a.iii.

    VI. Если поле является ссылкой на объект, и этот класс равен методу, сравнивает это поле путем рекурсивного вызова равных, рекурсивно вызывается hashCode в поле. Если требуется более сложное сравнение, вычислите "каноническое представление" для этого поля и вызовите hashCode на каноническое представление. Если значение поля равно нулю, возвращаем 0 (или некоторую другую константу, но 0 традиционно)

    VII. Если поле является массивом, рассматривайте его так, как если бы каждый элемент был отдельным полем. То есть вычислить хэш-код для каждого значимого элемента, применяя эти правила рекурсивно и объединить эти значения на шаг 2.b. Если каждый элемент в поле массива является значительным, вы можете использовать один из Методы Arrays.hashCode добавлены в версию 1.5.

    б. Объедините хэш-код c, вычисленный на шаге 2.a, в результате следующим образом:

       result = 31 * result + c;
    
  • Результат возврата.

  • Когда вы закончите писать метод hashCode, спросите себя, равные экземпляры имеют одинаковые хэш-коды. Напишите модульные тесты, чтобы проверить свою интуицию! Если равные экземпляры имеют неравные хэш-коды, выясните, почему и устраните проблему.

Разработчики языка Java и Eclipse, похоже, следуют аналогичным рекомендациям, я полагаю. Счастливое кодирование. Приветствия.

Ответ 2

С помощью Java 7 вы можете использовать java.util.Objects для написания коротких и изящных методов:

class Foo {
  private String name;
  private String id;

  @Override
  public int hashCode() {
    return Objects.hash(name,id);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Foo) {
      Foo right = (Foo) obj;
      return Objects.equals(name,right.name) && Objects.equals(id,right.id);
    }
    return false;
  }
}

Ответ 3

Да, это прекрасно:) Вы увидите этот подход почти везде в исходном коде Java.

Ответ 4

Как правило, это хорошо, но:

  • Guava делает это как-то лучше, я предпочитаю это. [EDIT: Кажется, что с JDK7 Java предоставляет аналогичную хеш-функцию].
  • Некоторые фреймворки могут вызвать проблемы при непосредственном доступе к полям вместо использования сеттеров/геттеров, например Hibernate. Для некоторых полей, которые Hibernate создает ленивый, он создает прокси не реальный объект. Только вызов getter приведет к тому, что Hibernate перейдет к реальному значению в базе данных.

Ответ 5

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

Ответ 6

Я также хотел бы добавить ссылку на пункт 9 в "Эффективном Java 2nd Edition" Джошуа Блоха.

Вот рецепт от Item 9 : ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS

  • Хранить некоторое постоянное ненулевое значение, например, 17, в переменной int, называемой результатом.
  • Для каждого значимого поля f в вашем объекте (каждое поле, принятое во внимание с помощью метода equals), выполните следующие действия:
    a. Compute an int hash code c for the field:            
        i.   If the field is a boolean, compute (f ? 1 : 0).
        ii.  If the field is a byte, char, short, or int, compute (int) f.
        iii. If the field is a long,compute(int)(f^(f>>>32)).
        iv.  If the field is a float, compute Float.floatToIntBits(f).
        v.   If the field is a double, compute Double.doubleToLongBits(f), and then hash the resulting long as in step 2.a.iii.
        vi.  If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a "canonical representation" for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).
        vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5. 
   b. Combine the hash code c computed in step 2.a into result as follows: result = 31 * result + c;
3. Return result.
4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition! If equal instances have unequal hash codes, figure out why and fix the problem.