Как правильно реализовать надежный метод equals () и hashCode () в иерархии наследования? - PullRequest
0 голосов
/ 14 ноября 2018

У меня есть следующий абстрактный Person класс:

import java.util.Objects;

public abstract class Person {

    protected String name;
    protected int id;

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public abstract String description();

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof Person)) return false;

        return Objects.equals(this.name, ((Person) obj).name) &&
                this.id == ((Person) obj).id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.name, this.id);
    }
}

И теперь у меня есть подкласс Person с именем Employee:

import java.time.LocalDate;
import java.util.Objects;

public class Employee extends Person {

    private double salary;
    private LocalDate hireDay;

    public Employee(String name, int id, double salary, int year, int month, int day) {
        super(name, id);
        this.salary = salary;
        this.hireDay = LocalDate.of(year, month, day);
    }

    @Override
    public String description() {
        return "Employee with a salary of " + this.salary;
    }

    @Override
    public int hashCode() {
        return super.hashCode() + Objects.hash(this.salary,this.hireDay);
    }

    @Override
    public boolean equals(Object obj) {

        return super.equals(obj) && 
        Double.compare(this.salary, ((Employee) obj).salary) == 0
              && Objects.equals(this.hireDay,((Employee)obj).hireDay);

}

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

Рефлексивный: x.equals (x) всегда True
Симметричный: x.equals (y) эквивалентен y.equals (x)
Транзитивно: x.equals (y) и y.equals (z) означает, что x.equals (z) имеет значение true

Когда я вызываю метод equals ()суперкласс внутри подкласса, я сначала гарантирую, что все сравниваемые объекты являются подклассами суперкласса.Эта проблема решает проблему сравнения смешанных типов и заботится о контракте, упомянутом выше.Мне больше не нужно использовать следующую реализацию equals:

    @Override
    public boolean equals(Object obj) {

        if(this == obj) return true;
        else if(obj == null || this.getClass() != obj.getClass()) return false;

        Employee other = (Employee) obj;

        return Objects.equals(this.name, other.name) &&
               Double.compare(this.salary, other.salary) == 0 &&
               Objects.equals(this.hireDay, other.hireDay);

    }

А именно, мне больше не нужно явно проверять, принадлежит ли текущий объект (this) к тому же классу, что и obj из-заметод в суперклассе, который использует оператор instance of.

Надежнее ли помещать эту реализацию в оператор равенства применительно к суперклассу или лучше использовать более явный тест в подклассе, используя метод getClass(), чтобы соответствовать контракту??

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

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

РЕДАКТИРОВАТЬ:

Я попросил Intellij реализовать метод equals и hashcode, и он решил использовать последнюю реализацию, которую я опубликовал выше.Тогда, при каких обстоятельствах я бы использовал instance of в суперклассе?Будет ли это, когда я реализую метод окончательного равенства в суперклассе, например, сравнивая только объекты Person на основе идентификатора пользователя?

Ответы [ 3 ]

0 голосов
/ 14 ноября 2018

Если вы хотите реализовать метод equals и hashcode, используйте eclipse, просто щелкните правой кнопкой мыши в файле, перейдите к исходному коду и выберите generate equals () & hashcode () с нужными полями, как показано ниже:

enter image description here

enter image description here

0 голосов
/ 14 ноября 2018

Вот мои заметки из чтения Effective Java 2nd Edition:

Равно должен придерживаться генерального контракта:

  • Рефлексивно: для non-null x: x.equals(x) == true
  • Симметричный: для non-null x,y: x.equals(y) <==> y.equals(x)
  • Переходный: для non-null x,y,z: x.equals(y) and y.equals(z) ==> x.equals(z) == true
  • Согласовано: для любого ненулевого x, y: если x.equals(y) == true, то для всех вызовов должно возвращаться true, если нет изменений в x и y
  • Нуль: для ненулевых x: x.equals(null) == false

Метод высокого качества:

  1. Использование == для проверки, является ли аргумент ссылкой на этот объект (x == x)
  2. Используйте instanceof, чтобы проверить, является ли аргумент правильным типом (также проверяет null)
  3. Приведите аргумент к правильному типу
  4. Для каждого "значимого" поля в классе проверьте, соответствует ли это поле аргумента соответствующему полю этого объекта
  5. После того, как сделано, проверьте, Симметричный, переходный и последовательный

Заключительные предостережения:

  • всегда переопределяет hashCode, когда вы переопределяете равно
  • Не пытайтесь быть слишком умным
  • не заменяйте другой тип для Object в объявлении equals -> Не стоит того, чтобы незначительное повышение производительности увеличивало сложность

Хэш-код прямая цитата из Effective Java 2nd Edition

  1. Сохраните некоторое постоянное ненулевое значение, скажем, 17, в переменной int, которая называется result.
  2. Для каждого значимого поля f в вашем объекте (каждое поле учитывается то есть метод equals), выполните следующие действия:

    • Вычислить int хеш-код c для поля:
      1. Если поле является логическим, вычислить (f ? 1 : 0).
      2. Если поле является byte, char, short, or int, compute (int) f.
      3. Если поле является long, compute (int) (f ^ (f >>> 32)).
      4. Если поле имеет значение float, compute Float.floatToIntBits(f).
      5. Если поле является double, compute Double.doubleToLongBits(f), и затем хешируем получившиеся long.
      6. Если поле является ссылкой на объект и метод equals этого класса сравнивает поле, рекурсивно вызывая equals, рекурсивно вызвать hashCode на поле. Если более сложное сравнение необходимо вычислить «каноническое представление» для этого поля и вызвать hashCode для канонического представления. Если значение поле null, return 0 (или некоторая другая константа, но 0 - традиционная).
      7. Если поле является массивом, обрабатывайте его так, как если бы каждый элемент был отдельным полем. То есть вычислить хеш-код для каждого значимого элемента, применив эти правила рекурсивно, и объединить эти значения в шаге 2.b. Если каждый элемент в поле массива является значительным, вы можете использовать один из Методы Arrays.hashCode добавлены в выпуске 1.5.
    • Объедините хэш-код c, вычисленный на шаге 2.a, в результат следующим образом: result = 31 * result + c;
  3. Возвращаемый результат.

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

так что следуйте этим правилам:

@Override
public boolean equals(Object obj) {

    if (this == obj) {
        return true;
    }

    if (!(obj instanceof Employee)) {
        return false;
    }
    Employee other = (Employee) obj;

    return super.equals(other) &&
           Double.compare(this.salary, other.salary) == 0 &&
           this.hireDay.equals(other.hireDay);

}

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

0 голосов
/ 14 ноября 2018

Возможно ли, чтобы два человека когда-либо имели один и тот же id?Это не должноТаким образом, эта логика распространяется на класс Employee, что означает, что реализации equals и hashCode в классе Person достаточно.

На данный момент, поскольку вы имеете дело только с int, вы можете использовать Integer.hashCode(id) для hashCode и просто сравнить значения для equals.

...