Java равны () и hashCode () на основе разных полей? - PullRequest
19 голосов
/ 20 января 2011

Есть ли ситуации, когда для класса имеет смысл реализовывать свои методы equals() и hashCode(), используя другой набор полей класса?Генератор Netbeans equals() и hashCode(), в котором вас просят выбрать поля для включения в каждый метод отдельно.Я всегда заканчиваю тем, что выбираю одни и те же поля для обоих методов, но есть ли ситуация, когда это не правильный выбор?

Ответы [ 6 ]

23 голосов
/ 20 января 2011

Ну, equals() должен использовать все поля, используемые hashCode(), иначе вы можете получить разные хэш-коды для одинаковых объектов.Однако обратное неверно - вы могли бы не выбирать одно конкретное поле при выборе хеш-кода.Таким образом, вы можете получить один и тот же хэш-код для двух неравных объектов, которые отличаются только этим «неиспользуемым» полем (в отличие от естественных коллизий).Вы бы хотели этого только в ситуации, когда вы знали, что столкновения маловероятны, но где вы собираетесь хэшировать лот .Я предполагаю, что это крайне редко:)

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

3 голосов
/ 20 января 2011

Джон Скит проделал хорошую работу, ответив на этот вопрос (как он всегда делает). Однако я хотел бы добавить, что это допустимая реализация для любой реализации, равной

public int hashCode() {
  return 42;
}

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

2 голосов
/ 20 января 2011

Как правило, вы должны использовать те же поля.Из документации equals():

Обратите внимание, что, как правило, необходимо переопределять метод hashCode при переопределении этого метода, чтобы поддерживать общий контракт для метода hashCode, в котором говорится, что равные объектыдолжен иметь одинаковые хеш-коды.

Из документации hashCode():

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

Обратите внимание, что обратное неверно - вы можете иметь два объекта с одинаковым хеш-кодом, которые не равны (Вот как некоторые структуры данных разрешают коллизии)

Теоретически этоМожно использовать подмножество полей метода equals(..) для метода hashCode(), но я не могу думать, есть ли практическая причина для этого.

1 голос
/ 14 января 2012

В продолжение ответа Джона Скита я недавно столкнулся со случаем, когда мне нужно было реализовать метод hashCode только с подмножеством полей, используемых в методе equals.(Упрощенный) сценарий таков:

У меня есть два класса A и B, каждый из которых содержит ссылку на другой в дополнение к определенному ключу String.Используя автоматический hashCode и генератор равенства в Eclipse (который, в отличие от Netbeans, дает возможность использовать одни и те же поля в обоих методах), я получаю следующие классы:

public class A {

    public B b;
    public String bKey;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((b == null) ? 0 : b.hashCode());
        result = prime * result + ((bKey == null) ? 0 : bKey.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof A))
            return false;
        A other = (A) obj;
        if (b == null) {
            if (other.b != null)
                return false;
        } else if (!b.equals(other.b))
            return false;
        if (bKey == null) {
            if (other.bKey != null)
                return false;
        } else if (!bKey.equals(other.bKey))
            return false;
        return true;
    }
}

public class B {

    public A a;
    public String aKey;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((a == null) ? 0 : a.hashCode());
        result = prime * result + ((aKey == null) ? 0 : aKey.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof B))
            return false;
        B other = (B) obj;
        if (a == null) {
            if (other.a != null)
                return false;
        } else if (!a.equals(other.a))
            return false;
        if (aKey == null) {
            if (other.aKey != null)
                return false;
        } else if (!aKey.equals(other.aKey))
            return false;
        return true;
    }
}

Проблема возникла, когдаЯ попытался добавить класс A в HashSet следующим образом:

    public static void main(String[] args) {

        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;

        Set<A> aSet = new HashSet<A>();
        aSet.add(a);
    }

Это закончится StackOverflowError, так как добавление a в aSet приведет к вызову метода a hashCode, что приведет к вызову b hashCode, что приведет к вызову a s hashCode, и т. д., и т. д., и т. д. Единственный способ обойти это - удалить ссылкув A из B hashCode и equals ИЛИ только включать String bKey в B метод hashCode.Так как я хотел, чтобы метод B.equals включал ссылку A в проверку на равенство, единственное, что я мог сделать, это заставить B.hashCode использовать только подмножество полей, которые использовались в B.equals, то есть использовать только B.bKeyв B.hashCode.Я не мог видеть другого пути решения этой проблемы.

Возможно, мой дизайн имеет недостатки, и я приветствую кого-то, кто это укажет, но, по сути, именно так мои доменные объекты структурированы в моей реальной программе.

1 голос
/ 20 января 2011

Я не думаю, что есть.Я ранее писал об этой теме - мне кажется, это недостаток пользовательского интерфейса в NetBeans, который позволяет вам выбирать их независимо друг от друга.Из моего сообщения в блоге:

Этот пост от bytes.com хорошо объясняет это:

Переопределение метода hashCode.

В контракте для метода equals действительно должна быть другая строка, в которой говорится, что вы должны перейти к переопределению метода hashCode после переопределения метода equals.Метод hashCode поддерживается в интересах коллекций на основе хеша.

Контракт

Снова из спецификации:

Всякий раз, когда он вызывается для одного и того же объекта более одного раза вПри выполнении приложения метод hashCode должен последовательно возвращать одно и то же целое число, при условии, что никакая информация, используемая в сравнениях сравнения на объекте, не изменяется.Это целое число не должно оставаться согласованным при выполнении одного приложения другим исполнением того же приложения.Если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объектов должен привести к одному и тому же целочисленному результату.Не требуется, чтобы, если два объекта были неравны в соответствии с методом equals, то вызов метода hashCode для каждого из двух объектов должен давать разные целочисленные результаты.Тем не менее, программист должен знать, что выдача различных целочисленных результатов для неравных объектов может повысить производительность хеш-таблиц.Таким образом, равные объекты должны иметь одинаковые хэш-коды.Простой способ гарантировать, что это условие всегда выполняется, состоит в том, чтобы использовать те же атрибуты, которые используются при определении равенства при определении hashCode.Теперь вы должны понять, почему важно переопределять hashCode каждый раз, когда вы переопределяете равно.

Это предложение из последнего абзаца подводит итог: « Простой способ убедиться, что это условие всегдаудовлетворено использование тех же атрибутов, которые используются для определения равенства при определении хеш-кода ”.

0 голосов
/ 20 января 2011

Прочитайте Эффективную Java в главе 3: « Всегда переопределяйте hashCode, когда вы переопределяете равно ».

И я думаю, что если ваш объект никогда не будет помещен в коллекцию на основе хеша, вы нене нужно переопределять hashCode.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...