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

mongo db_name --eval "db.user_info.find().forEach(function(o) {print(o._id);})"
617
задан George Stocker 11 August 2014 в 19:02
поделиться

7 ответов

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

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

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

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

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

На практике:

, Если Вы переопределяете один, тогда необходимо переопределить другой.

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

Использование превосходные классы помощника EqualsBuilder и HashCodeBuilder от библиотека Commons Lang Apache. Пример:

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

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

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

1415
ответ дан 18 revs, 10 users 77% 11 August 2014 в 19:02
поделиться

Существуют некоторые проблемы, которые стоит заметить, имеете ли Вы дело с классами, которые сохраняются с помощью Картопостроителя Объектных Отношений (ORM), любят, в спящем режиме, если Вы не думали, что это уже было необоснованно сложно!

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

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

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

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

, Если Вы имеете дело с ORM, удостоверьтесь, что всегда использовали методов get, и никогда ссылки поля в [1 111] и equals().

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

, Постоянные объекты часто используют id поле для удержания клавиши объекта. Это поле будет автоматически обновлено, когда объект будет сначала сохранен. Не используйте идентификационное поле в [1 114]. Но можно использовать его в [1 115].

шаблон А, который я часто использую,

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

, Но: Вы не можете включать getId() в [1 117]. Если Вы делаете, когда объект сохраняется, hashCode изменения. Если объект будет в HashSet, Вы никогда не будете находить его снова.

В моем Person пример, я, вероятно, использовал бы getName() для [1 122] и getId() плюс [1 124] (только для паранойи) для [1 125]. Это хорошо, если существует некоторый риск "коллизий" для [1 126], но никогда хорошо для [1 127].

hashCode() должен использовать не изменяющееся подмножество свойств от [1 129]

287
ответ дан maaartinus 11 August 2014 в 19:02
поделиться

Для благоприятной для наследования реализации проверьте решение Tal Cohen, , Как я Правильно Реализую равняние () Метод?

Сводка:

В его книге Эффективное Руководство Языка программирования Java (Addison-Wesley, 2001), Joshua Bloch утверждает, что "Нет просто никакого способа расширить instantiable класс и добавить аспект, в то время как сохранение равняется контракту". Tal не соглашается.

Его решение состоит в том, чтобы реализовать, равняется () путем называния другого несимметричного 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);
    }
}

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

44
ответ дан reevesy 11 August 2014 в 19:02
поделиться

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

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

6
ответ дан Darren Greaves 11 August 2014 в 19:02
поделиться

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

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

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

Опция № 2 позволяет классу быть безопасно расширенным без переопределения, равняется или повреждающаяся симметрия.

, Если Ваш класс также Comparable, equals и compareTo, методы должны быть последовательными также. Вот шаблон для, равняется методу в 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;
  }

}
18
ответ дан erickson 11 August 2014 в 19:02
поделиться

Разъяснение о 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;

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

, Если Вы хотите гарантировать симметричное условие. a=b, если b=a и вызов принципа замены Лисков super.equals(other) не только в случае [1 113] экземпляр, но и проверка после на [1 114] экземпляр:

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 не ссылка [1 116], то это могло бы быть быть ссылкой класса A (потому что Вы расширяете его), в этом случае Вы звоните super.equals() также .

83
ответ дан BartoszKP 11 August 2014 в 19:02
поделиться

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

15
ответ дан Johannes Schaub - litb 12 August 2014 в 06:02
поделиться
  • 1
    В прошлый раз, когда я проверил, массивы не могут быть изменены после того, как они создаются. Они - фиксированная длина. Можно преобразовать массив в список, изменить его размер, затем преобразовать тот назад в другой массив, но Вы не можете изменить размеры массива непосредственно. – danludwig 29 May 2013 в 01:16
Другие вопросы по тегам:

Похожие вопросы: