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

Java

Что такое равны методам Hashcode?

Это начинается с класса Object. Мы знаем, что класс Object является суперклассом Java. Каждый класс прямо или косвенно наследует класс Object. Он предоставляет 8 основных методов в Object, среди которых два метода equals и метод hashcode.

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

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

Зачем переопределять методы equals и hashcode?

Почему вам нужно переопределить метод equals и метод hashcode, я думаю, что это в основном основано на следующих двух моментах:

1. Мы уже знаем, что метод equals в Object используется для оценки того, одинаковы ли ссылки двух объектов, но иногда нам не нужно судить, равны ли ссылки двух объектов, нам нужно только определенное состояние объекта. два объекта равны. Например, для двух статей мне нужно только судить, одинаковы ли ссылки двух статей.Если ссылки одинаковы, то это одна и та же статья, и мне не нужно сравнивать другие атрибуты или ссылка адреса те же.

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

Мы используем небольшую демонстрацию для моделирования специального сценария, чтобы мы могли лучше понять, почему нам нужно переписать методы equals и hashcode.Наш сценарий: у нас много статей, и мне нужно судить, существует ли статья уже в наборе. , две статьи Одно и то же условие - путь доступа одинаков.

Что ж, давайте вместе напишем демо. Давайте создадим класс статьи для хранения информации о статье. Класс статьи специально разработан следующим образом:

class Article{
    // 文章路径
    String url;

    // 文章标题
    String title;
    public Article(String url ,String title){
        this.url = url;
        this.title = title;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}

Класс article имеет два атрибута, path и title.В этом классе мы не переопределяли методы equals и hashcode, поэтому мы будем использовать методы equals и hashcode в объекте суперкласса, если вы не видели equals в Класс объекта И метод hashcode, давайте сначала посмотрим на методы equals и hashcode в классе Object:

После прочтения, затем мы пишем тестовый класс, код тестового класса выглядит следующим образом:

public class EqualsAndHashcode {
    public static void main(String[] args) {
        Article article = new Article("www.baidu.com","百度一下");
        Article article1 = new Article("www.baidu.com","坑B百度");

        Set<Article> set = new HashSet<>();
        set.add(article);
        System.out.println(set.contains(article1));

    }
}

В тестовом классе мы инстанцируем два объекта article, URL-адрес объекта article один и тот же, заголовок отличается, мы помещаем объект article в набор, определяем, существует ли объект Article1 в Set, согласно нашему предположению. две статьи имеют один и тот же URL-адрес, тогда две статьи должны быть одной и той же статьей, поэтому здесь я должен вернуться к TRUE, и мы запускаем метод main. Результат выглядит следующим образом:

Мы видим, что результат не тот True, который вы хотите, а False, Причина очень проста, потому что путь доступа к двум статьям - это одна и та же статья, это правило, которое мы определили, мы не сообщали нашей программе это правило, мы Методы equals и hashcode не переопределяются, поэтому при оценке система использует методы equals и hashcode по умолчанию класса Object. Метод equals по умолчанию оценивает, совпадают ли ссылочные адреса двух объектов, которые здесь должны быть разными. ответ Ложный. Нам нужно указать нашей программе правила равенства, поэтому мы переопределяем метод equals.

1. Переопределить метод equals

Здесь мы сначала используем метод equals, сгенерированный инструментом IDEA, и изменим окончательную логическую логику возврата.Ниже мы представим конкретные правила написания. Наконец, наш метод equals выглядит следующим образом

/**
 * 重写equals方法,只要两篇文章的url相同就是同一篇文章
 * @param o
 * @return
 */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Article article = (Article) o;
        return Objects.equals(url, article.url);
    }

Запустите метод Main еще раз, вы обнаружите, что он все еще False, почему это так? Я уже рассказал программе логику оценки равенства двух объектов. Не волнуйтесь, давайте сначала поговорим о хеш-таблице. Мы знаем, что хеш-таблица принимает структуру массив + связанный список, и каждый массив монтируется с связанный список. , узлы связанного списка используются для хранения информации об объекте, а положение объекта, попадающего в массив, определяется функцией hashcode(). Поэтому, когда мы вызываем метод add(Object o) HashSet, мы сначала находим соответствующую позицию в массиве в соответствии с возвращаемым значением o.hashCode().Если в позиции массива нет узла, мы помещаем здесь o. Если есть узел, o присоединяется к концу связанного списка. Точно так же при вызове contains(Object o) Java находит соответствующую позицию в массиве с помощью возвращаемого значения hashCode(), а затем вызывает метод equals() по очереди для узлов в соответствующем связанном списке, чтобы определить, является ли объект это тот, который вы хотите.

Поскольку мы переписали только метод equals, а не метод хэш-кода, значения хэш-кода двух статей различаются, поэтому позиции, сопоставленные с массивом, различны.При вызове метода set.contains(article1) в хеш-таблице Ситуация может быть такой, как показано на следующем рисунке:

Объект article сопоставляется с позицией индекса 0 в массиве, а объект article1 сопоставляется с позицией индекса 6 массива, поэтому он возвращает False, если он не найден. Поскольку недостаточно просто переопределить метод equals, мы также переопределяем метод hashcode.

2. Перепишите метод хэш-кода

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

    @Override
    public int hashCode() {
        return Objects.hash(url);
    }

После перезаписи метода хэш-кода снова запустите метод Main, на этот раз результатом будет True, а это и есть тот результат, который нам нужен. После переопределения методов equals и hashcode поиск в хеш-таблице выглядит так:

Во-первых, объект article1 также будет сопоставлен с позицией индекса массива 1, а узел данных статьи находится в позиции индекса массива 1, поэтому будет выполнена команда article1.equals(article), потому что у нас есть переписал метод equals объекта Article, будет ли он судить о том, равны ли свойства URL двух объектов Article, и если они равны, вернуть True, что здесь очевидно равно, поэтому верните True здесь, чтобы получить результат, который мы хотим .

Как написать методы equals и hashcode?

Нужно переопределить метод equals самостоятельно? Хорошо, я перепишу это и взломал следующий код:

public boolean equals(Article o) {
    if (this == o) return true;
    if (o == null || !(o instanceof  Article)) return false;
    return o.url.equals(url);
}

Это правильно? Хотя логика внутри не кажется проблемой, параметр метода equals стал статьей. На самом деле, вы не имеете ничего общего с переопределением метода equals, это полностью переопределяет метод equals с типом параметра Article и не переопределяет метод equals в классе Object.

Итак, как переопределить метод equals? На самом деле метод equals имеет общие положения. Когда вы переопределяете метод equals, вам необходимо переопределить общие соглашения метода equals. В Object существуют следующие спецификации: Метод equals реализует отношение эквивалентности. Он обладает такими свойствами:

  • рефлексивность: x.equals(x) должен возвращать true для любой непустой ссылки x
  • симметрия: Для любых ненулевых ссылок x и y функция x.equals(y) должна возвращать значение true тогда и только тогда, когда y.equals(x) возвращает значение true.
  • переходность: Для любой непустой ссылки x, y, z, если x.equals(y) возвращает true, а y.equals(z) возвращает true, тогда x.equals(z) должен возвращать true
  • последовательность: Для любых ненулевых ссылок x и y многократные вызовы x.equals(y) должны всегда возвращать true или всегда возвращать false, если информация, используемая при сравнении равенства, не была изменена.
  • непустой: x.equals(null) должен возвращать false для любой ненулевой ссылки x

Теперь, когда мы знаем общее соглашение о написании метода equals, давайте снова перепишем метод equals() объекта Article со ссылкой на общее соглашение о переопределении метода equals. код показывает, как показано ниже:

    // 使用 @Override 标记,这样就可以避免上面的错误
    @Override
    public boolean equals(Object o) {
        // 1、判断是否等于自身
        if (this == o) return true;
        // 2、判断 o 对象是否为空 或者类型是否为 Article 
        if (o == null || !(o instanceof  Article)) return false;
        // 3、参数类型转换
        Article article = (Article) o;
        // 4、判断两个对象的 url 是否相等
        return article.url.equals(url);
    }

На этот раз мы используем тег @Override, чтобы избежать нашей предыдущей ошибки перезаписи, потому что в родительском классе нет метода с параметром Article, поэтому компилятор сообщит об ошибке, что очень удобно для программистов. Затем мы выполняем рефлексивную и непустую проверку и, наконец, определяем, равны ли URL-адреса двух объектов. Этот метод equals намного лучше, чем описанный выше, и в принципе в нем нет ничего плохого.

Книга Effective-Java обобщает набор рецептов для написания высококачественных методов equals следующим образом:

  • 1. Используйте оператор ==, чтобы проверить, является ли параметр ссылкой на объект. Если это так, верните true.
  • 2. Используйте оператор instanceof, чтобы проверить, имеет ли параметр правильный тип. Если нет, вернуть false.
  • 3. Параметр преобразуется в правильный тип. Поскольку операция преобразования уже обрабатывается в instanceof , она обязательно завершится успешно.
  • 4. Для каждого «важного» атрибута в классе проверьте, соответствует ли атрибут параметра соответствующему атрибуту объекта.

Мы уже поняли, как переписать метод equals. Теперь давайте узнаем, как переписать метод hashcode. Мы знаем, что метод hashcode возвращает метод типа int, поэтому это легко сделать, просто перепишите его следующим образом.

@Override
 public int hashCode() { 
 return 1; 
 }

Это правильно? Правильно это или нет, давайте посмотрим на правила хеш-кода в Object:

  • 1. Когда метод hashCode повторно вызывается для объекта во время выполнения приложения, если никакая информация не была изменена при сравнении метода equals, он всегда должен возвращать одно и то же значение. Значение, возвращаемое при каждом выполнении одного приложения другому, может быть несовместимым.
  • 2. Если два объекта сравниваются равными в соответствии с методом equals(Object), то вызов hashCode для двух объектов должен давать одно и то же целое число.
  • 3, если два объекта не равны в соответствии с методом сравнения равных (объект) на каждом вызове объекта hashCode должны давать разные результаты не требуется.

Согласно указанному хэшкоду, кажется, что нет проблем, но вы должны знать хэш-таблица. Если вы пишете, для уравнения Hashmap и HashSet вы будете выпускать их напрямую, в Har Change, отображение элемента, отображаемое из групп Определяется HashCode, и наш Hashcode всегда возвращается 1, в котором каждый элемент отображает одно и то же местоположение, таблица HASH также ухудшается в список.

В сочетании со спецификацией хэш-кода и хеш-таблицей, чтобы переписать высококачественный метод хэш-кода, необходимо убедиться, что каждый элемент генерирует другое значение хэш-кода.В JDK каждый ссылочный тип переписывает функцию хэш-кода.Давайте посмотрим, как хэш-код в Класс String переписан:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Этот метод hashcode все еще очень хорошо написан. Лично я предпочитаю использовать официальные вещи. Я думаю, что они должны учитывать намного больше, чем мы, поэтому метод hashcode нашего класса Article можно написать так

    /**
     * 重写 hashcode方法,根据url返回hash值
     * @return
     */
    @Override
    public int hashCode() {
        return url.hashCode();
    }

Мы напрямую вызываем метод hashcode объекта String. На данный момент наш метод equals и метод hashcode были переписаны и, наконец, заканчиваются сводкой в ​​Effective-java.

  • 1. При переписывании метода equals также переписывайте метод hashCode.
  • 2. Не позволяйте методу equals быть слишком умным.
  • 3. В объявлении метода при равенстве не заменять параметр Object другими типами.

Недостаточно статей, я надеюсь, что все будут направлять, учиться вместе и вместе добиваться прогресса.

Наконец

Сделайте небольшую рекламу, добро пожаловать, чтобы отсканировать код и подпишитесь на общедоступную учетную запись WeChat: «Технический блог брата Пинтоу», давайте вместе добьемся прогресса.

平头哥的技术博文