Какие вопросы следует учитывать при переопределении равных и хэш-код в Java?

голоса
617

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

Задан 26/08/2008 в 09:50
источник пользователем
На других языках...                            


11 ответов

голоса
1k

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

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

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();
    }
}

Кроме того, помните:

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

Ответил 26/08/2008 в 10:12
источник пользователем

голоса
280

Есть некоторые вопросы, стоит заметить, если вы имеете дело с классами, которые сохраняются с использованием объектных отношений Mapper (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

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

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

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

Если вы имеете дело с ОРМ, убедитесь , что всегда использовать методы получения и никогда ссылки на поля в hashCode()и equals().

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

Устойчивые объекты часто используют 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()плюс getName()(только для паранойи) для equals(). Это нормально , если есть некоторый риск «столкновений» для hashCode(), но не хорошо для equals().

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

Ответил 02/11/2008 в 03:58
источник пользователем

голоса
78

Разъяснение о 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))результат выдает так, как это должно быть.

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

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

Очевидно, что это не так.

Если вы хотите , чтобы обеспечить симметричное состояние. а = Ь , если Ь = а и замену принципа вызова Лиск 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() тоже .

Ответил 28/08/2008 в 14:16
источник пользователем

голоса
41

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

Резюме:

В своей книге Эффективное Java Programming Language Guide (Addison-Wesley, 2001), Джошуа Блох утверждает , что «Там просто нет способа направить инстанциируемый класс и добавить аспект при сохранении контракта на равных.» Таль не согласен.

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

Пример:

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);
    }
}

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

Ответил 11/09/2008 в 04:06
источник пользователем

голоса
31

Тем не менее поражен, что никто не рекомендовал библиотеку гуавы для этого.

 //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());
    }
Ответил 12/02/2013 в 08:17
источник пользователем

голоса
23

Есть два метода в родительском классе как 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

Повеселись! @. @

Ответил 20/12/2013 в 07:14
источник пользователем

голоса
18

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

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

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

Вариант № 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;
  }

}
Ответил 28/08/2008 в 19:25
источник пользователем

голоса
15

Для равных, заглянуть в секреты Равных по Angelika Langer . Я люблю это очень сильно. Она также большой FAQ о Обобщения в Java . Посмотреть другие ее статьи здесь (прокрутите вниз до «Core Java»), где она также идет по Части-2 и «сравнения смешанного типа». Весело читать их!

Ответил 27/02/2009 в 23:05
источник пользователем

голоса
11

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

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

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

Реализация по умолчанию приведены в хэш-код () в классе 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
Ответил 24/10/2013 в 11:56
источник пользователем

голоса
7

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

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

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

Ответил 24/03/2013 в 15:34
источник пользователем

голоса
6

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

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

Ответил 02/09/2008 в 22:06
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more