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

Java equal() и hashCode() на основе разных полей?

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

Джон Скит отлично справился с этим вопросом (как всегда). Однако я хотел бы добавить, что это допустимая реализация для любой реализации equals
public int hashCode() {
  return 42;
}

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

Ответ 4

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

Эта сообщение from bytes.com дает хорошую работу для объяснения этого:

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

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

Контракт

Опять же из спецификаций:

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

Это предложение из последнего абзаца подводит итог: " Легкий способ гарантировать, что это условие всегда выполняется, - это использовать те же атрибуты, которые используются при определении равенства при определении hashCode" .

Ответ 5

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

У меня есть два класса A и B, каждый из которых содержит ссылку на другую в дополнение к определенному строковому ключу. Используя автоматический hashCode и equals generator в 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 hashCode, и т.д. и т.д. и т.д. Единственный способ обойти это - либо удалить ссылку на A из B hashCode, либо equals ИЛИ включить только String bKey в B hashCode. Поскольку я хотел, чтобы метод B.equals включал ссылку A в проверку равенства, единственное, что я мог сделать, это сделать B.hashCode использовать только подмножество полей, которые использовались в B.equals, т.е. использовать только B.bKey в B.hashCode. Я не видел другого пути.

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