Какие проблемы следует учитывать при переопределении равных и hashCode в Java?

Какие проблемы/ошибки следует учитывать при переопределении equals и hashCode?

Ответы

Ответ 1

Теория (для юристов языка и математически склонная):

equals() (javadoc) должен определить отношение эквивалентности (он должен быть рефлексивным, симметричным и транзитивным). Кроме того, он должен быть последовательным (если объекты не изменены, то он должен продолжать возвращать одно и то же значение). Кроме того, o.equals(null) всегда должен возвращать false.

hashCode() (javadoc) также должен быть последовательным (если объект не изменяется в терминах equals(), он должен продолжать возвращать то же значение).

отношение между двумя методами:

Всякий раз, когда a.equals(b), тогда a.hashCode() должен быть таким же, как b.hashCode().

На практике:

Если вы переопределите один, тогда вы должны переопределить другой.

Используйте тот же набор полей, который вы используете для вычисления equals() для вычисления hashCode().

Используйте отличные вспомогательные классы EqualsBuilder и HashCodeBuilder из библиотеки Apache Commons Lang. Пример:

public class Person {
    private String name;
    private int age;
    // ...

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
            // if deriving: appendSuper(super.hashCode()).
            append(name).
            append(age).
            toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
       if (!(obj instanceof Person))
            return false;
        if (obj == this)
            return true;

        Person rhs = (Person) obj;
        return new EqualsBuilder().
            // if deriving: appendSuper(super.equals(obj)).
            append(name, rhs.name).
            append(age, rhs.age).
            isEquals();
    }
}

Также помните:

При использовании хэш-функции Collection или Map, например HashSet, LinkedHashSet, HashMap, Hashtable или WeakHashMap, убедитесь, что hashCode() объектов-ключей, которые вы помещаете в коллекцию, никогда не изменяется, пока объект находится в коллекции. Пуленепробиваемый способ обеспечить это, чтобы ваши ключи были неизменными, который имеет и другие преимущества.

Ответ 2

Есть некоторые проблемы, заслуживающие внимания, если вы имеете дело с классами, которые сохраняются с использованием Object-Relationship Mapper (ORM), например Hibernate, если вы не думаете, что это было необоснованно сложным уже!

Ленивые загруженные объекты - это подклассы

Если ваши объекты сохраняются с использованием ORM, во многих случаях вы будете иметь дело с динамическими прокси-серверами, чтобы избежать слишком рано загружать объект из хранилища данных. Эти прокси реализованы как подклассы вашего собственного класса. Это означает, что this.getClass() == o.getClass() вернет false. Например:

Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy

Если вы имеете дело с ORM, использование o instanceof Person - это единственное, что будет вести себя правильно.

Ленивые загруженные объекты имеют нулевые поля

ORM обычно используют геттеры для принудительной загрузки ленивых загружаемых объектов. Это означает, что person.name будет null, если person загружается с лени, даже если person.getName() принудительно загружает и возвращает "John Doe". По моему опыту, это чаще встречается в hashCode() и equals().

Если вы имеете дело с ORM, обязательно используйте геттеры и никогда не ссылайтесь на ссылки в hashCode() и equals().

Сохранение объекта изменит его состояние

Устойчивые объекты часто используют поле id для хранения ключа объекта. Это поле будет автоматически обновляться при первом сохранении объекта. Не используйте поле id в hashCode(). Но вы можете использовать его в equals().

Я часто использую шаблон

if (this.getId() == null) {
    return this == other;
}
else {
    return this.getId().equals(other.getId());
}

Но: вы не можете включить getId() в hashCode(). Если вы это сделаете, когда объект сохраняется, изменяется его hashCode. Если объект находится в HashSet, вы "никогда" не найдете его снова.

В моем примере person я, вероятно, использовал бы getName() для hashCode и getId() plus getName() (только для паранойи) для equals(). Это нормально, если есть риск "столкновений" для hashCode(), но никогда не подходит для equals().

hashCode() должен использовать неизменяемое подмножество свойств из equals()

Ответ 3

Осветление о obj.getClass() != getClass().

Это утверждение является результатом нецелевого наследования equals(). JLS (спецификация языка Java) указывает, что если A.equals(B) == true, то B.equals(A) также должен возвращать true. Если вы опускаете этот оператор, наследующий классы, которые переопределяют equals() (и меняют его поведение), это приведет к нарушению этой спецификации.

Рассмотрим следующий пример того, что происходит, когда оператор опущен:

    class A {
      int field1;

      A(int field1) {
        this.field1 = field1;
      }

      public boolean equals(Object other) {
        return (other != null && other instanceof A && ((A) other).field1 == field1);
      }
    }

    class B extends A {
        int field2;

        B(int field1, int field2) {
            super(field1);
            this.field2 = field2;
        }

        public boolean equals(Object other) {
            return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
        }
    }    

Выполнение new A(1).equals(new A(1)) Кроме того, результат new B(1,1).equals(new B(1,1)) выдаёт true, как и должно быть.

Это выглядит очень хорошо, но посмотрите, что произойдет, если мы попытаемся использовать оба класса:

A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;

Очевидно, что это неправильно.

Если вы хотите обеспечить симметричное условие. a = b, если b = a и принцип подстановки Лискова вызывают super.equals(other) не только в случае экземпляра B, но и проверяют для экземпляра A:

if (other instanceof B )
   return (other != null && ((B)other).field2 == field2 && super.equals(other)); 
if (other instanceof A) return super.equals(other); 
   else return false;

Будет выводиться:

a.equals(b) == true;
b.equals(a) == true;

Где, если A не является ссылкой B, то это может быть ссылка класса A (потому что вы его расширяете), в этом случае вы вызываете super.equals() тоже.

Ответ 4

Для реализации, основанной на наследовании, проверьте решение Tal Cohen, Как правильно реализовать метод equals()?

Резюме:

В своей книге Эффективное руководство по языку программирования Java (Addison-Wesley, 2001), Джошуа Блох утверждает, что "просто нет возможности расширить реальный класс и добавить аспект, сохраняя при этом равный контракт". Таль не согласен.

Его решение состоит в том, чтобы реализовать equals(), вызвав другой несимметричный blindlyEquals() в обоих направлениях. blindlyEquals() переопределяется подклассами, equals() наследуется и никогда не переопределяется.

Пример:

class Point {
    private int x;
    private int y;
    protected boolean blindlyEquals(Object o) {
        if (!(o instanceof Point))
            return false;
        Point p = (Point)o;
        return (p.x == this.x && p.y == this.y);
    }
    public boolean equals(Object o) {
        return (this.blindlyEquals(o) && o.blindlyEquals(this));
    }
}

class ColorPoint extends Point {
    private Color c;
    protected boolean blindlyEquals(Object o) {
        if (!(o instanceof ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint)o;
        return (super.blindlyEquals(cp) && 
        cp.color == this.color);
    }
}

Обратите внимание, что equals() должно работать через иерархии наследования, если необходимо выполнить Принцип замещения Лискова.

Ответ 5

Все еще удивляюсь, что ни один из них не рекомендовал библиотеку guava для этого.

 //Sample taken from a current working project of mine just to illustrate the idea

    @Override
    public int hashCode(){
        return Objects.hashCode(this.getDate(), this.datePattern);
    }

    @Override
    public boolean equals(Object obj){
        if ( ! obj instanceof DateAndPattern ) {
            return false;
        }
        return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
                && Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
    }

Ответ 6

В суперклассе есть два метода: java.lang.Object. Нам нужно переопределить их в пользовательский объект.

public boolean equals(Object obj)
public int hashCode()

Равные объекты должны выдавать один и тот же хэш-код, если они равны, однако неравные объекты не должны создавать отдельные хэш-коды.

public class Test
{
    private int num;
    private String data;
    public boolean equals(Object obj)
    {
        if(this == obj)
            return true;
        if((obj == null) || (obj.getClass() != this.getClass()))
            return false;
        // object must be Test at this point
        Test test = (Test)obj;
        return num == test.num &&
        (data == test.data || (data != null && data.equals(test.data)));
    }

    public int hashCode()
    {
        int hash = 7;
        hash = 31 * hash + num;
        hash = 31 * hash + (null == data ? 0 : data.hashCode());
        return hash;
    }

    // other methods
}

Если вы хотите получить больше, пожалуйста, проверьте эту ссылку как http://www.javaranch.com/journal/2002/10/equalhash.html

Это еще один пример, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html

Получайте удовольствие! @. @

Ответ 7

Есть несколько способов сделать проверку на равенство классов перед проверкой равенства членов, и я думаю, что оба они полезны при правильных обстоятельствах.

  • Используйте оператор instanceof.
  • Используйте this.getClass().equals(that.getClass()).

Я использую # 1 в реализации final equals или при реализации интерфейса, который предписывает алгоритм для equals (например, интерфейсы коллекции java.util;— правильный способ проверить с помощью (obj instanceof Set) или любого другого интерфейса, повторное внедрение). Это обычно плохой выбор, когда equals можно переопределить, поскольку это нарушает свойство симметрии.

Вариант № 2 позволяет безопасно расширять класс без превышения равных или нарушения симметрии.

Если ваш класс также Comparable, методы equals и compareTo также должны быть последовательными. Здесь шаблон для метода equals в классе Comparable:

final class MyClass implements Comparable<MyClass>
{

  …

  @Override
  public boolean equals(Object obj)
  {
    /* If compareTo and equals aren't final, we should check with getClass instead. */
    if (!(obj instanceof MyClass)) 
      return false;
    return compareTo((MyClass) obj) == 0;
  }

}

Ответ 8

Для равных посмотрите Секреты равных Анжелика Лангер. Я люблю это очень сильно. Она также очень часто задает вопрос о Generics в Java. Просмотрите ее другие статьи здесь (прокрутите вниз до "Core Java" ), где она также продолжит работу с Part-2 и "смешанное сравнение типов". Получайте удовольствие, читая их!

Ответ 9

Метод equals() используется для определения равенства двух объектов.

поскольку значение int равно 10, всегда равно 10. Но метод equals() равен равенству двух объектов. Когда мы говорим объект, у него будут свойства. Для решения вопроса о равенстве рассматриваются эти свойства. Нет необходимости, чтобы все свойства были приняты во внимание, чтобы определить равенство, и в отношении определения класса и контекста его можно решить. Тогда метод equals() может быть переопределен.

мы всегда должны переопределять метод hashCode() всякий раз, когда мы переопределяем метод equals(). Если нет, что произойдет? Если мы используем hashtables в нашем приложении, он не будет вести себя так, как ожидалось. Поскольку hashCode используется для определения сохраненного значения значений, он не возвращает правильное соответствующее значение для ключа.

Приведенная реализация по умолчанию - метод hashCode() в классе Object использует внутренний адрес объекта и преобразует его в целое число и возвращает его.

public class Tiger {
  private String color;
  private String stripePattern;
  private int height;

  @Override
  public boolean equals(Object object) {
    boolean result = false;
    if (object == null || object.getClass() != getClass()) {
      result = false;
    } else {
      Tiger tiger = (Tiger) object;
      if (this.color == tiger.getColor()
          && this.stripePattern == tiger.getStripePattern()) {
        result = true;
      }
    }
    return result;
  }

  // just omitted null checks
  @Override
  public int hashCode() {
    int hash = 3;
    hash = 7 * hash + this.color.hashCode();
    hash = 7 * hash + this.stripePattern.hashCode();
    return hash;
  }

  public static void main(String args[]) {
    Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
    Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
    Tiger siberianTiger = new Tiger("White", "Sparse", 4);
    System.out.println("bengalTiger1 and bengalTiger2: "
        + bengalTiger1.equals(bengalTiger2));
    System.out.println("bengalTiger1 and siberianTiger: "
        + bengalTiger1.equals(siberianTiger));

    System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
    System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
    System.out.println("siberianTiger hashCode: "
        + siberianTiger.hashCode());
  }

  public String getColor() {
    return color;
  }

  public String getStripePattern() {
    return stripePattern;
  }

  public Tiger(String color, String stripePattern, int height) {
    this.color = color;
    this.stripePattern = stripePattern;
    this.height = height;

  }
}

Пример вывода кода:

bengalTiger1 and bengalTiger2: true 
bengalTiger1 and siberianTiger: false 
bengalTiger1 hashCode: 1398212510 
bengalTiger2 hashCode: 1398212510 
siberianTiger hashCode: –1227465966

Ответ 10

Логически мы имеем:

a.getClass().equals(b.getClass()) && a.equals(b)a.hashCode() == b.hashCode()

Но не наоборот!

Ответ 11

Один найденный мной вопрос: где два объекта содержат ссылки друг на друга (один пример - отношение родителя/ребенка с методом удобства родителя, чтобы получить всех детей).
Подобные вещи довольно распространены, например, при отображении Hibernate.

Если вы включаете оба конца отношений в свой хэш-код или равные тесты, можно попасть в рекурсивный цикл, который заканчивается в исключении StackOverflowException.
Самое простое решение - не включать коллекцию getChildren в методы.