Почему в equals несколько return?

Рейтинг: 0Ответов: 2Опубликовано: 23.07.2023

Для класса Person со строковыми полями firstName и lastName общий вариант для реализации equals:

@Override
public boolean equals(Object o) {
    // self check
    if (this == o)return true; //1
    // null check
    if (o == null) return false; //2
    // type check and cast
    if (getClass() != o.getClass()) return false; //3
    Person person = (Person) o;
    // field comparison
    return Objects.equals(firstName, person.firstName) //4
        && Objects.equals(lastName, person.lastName);
}

Почему в одном методе перед основным (4)return еще три return? Смысл первого return - если ссылка на разные объекты одна и та же, то они равны. Зачем тогда в конце основной return //4?

Например, упрощу метод для понимания и на выходе получу 4. Зачем нужны первые 3 return?

public int test(Object o) {
    if (this == o) return 1; //Рефлексивность
    if (o == null) return 2;//Неравенство с null
    if (getClass() != o.getClass()) return 3; //симметричность
    
    return 4;
}

Какие поля в классе:

public class MyClass {
    double a;
    Object b;
    private int c; //не участвует в сравнении
    
    public static void main(String[] args) {
        MyClass test1 = new MyClass();
        test1.a = 3.2;
        test1.b = new String("Sек");
        MyClass test2 = new MyClass();
        System.out.println();
        test2.a = 3.2;
        test2.b = new String("Sек");
        //  System.out.println(test1.equals(test2));
        System.out.println(test1.test(test2));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true; //Рефлексивность
        if (o == null) return false;//Неравенство с null
        if (getClass() != o.getClass()) return false; //симметричность
        MyClass other = (MyClass) o;
        //Симметричность, транзитивность
        return other.a == this.a && (this.b == null && other.b == null || this.b.equals(other.b));
    }
}

Ответы

▲ 0Принят

Никто не запрещает переопределить метод equals "по-своему", оставив в нём базовую проверку и вынося проверку полей в отдельный защищённый метод:

public class Foo {
    private int bar;
    private String baz;

    @Override
    public boolean equals(Object that) {
        return this == that || (that != null && getClass() == that.getClass() && isEqual((Foo) that));
    }

    // Здесь that гарантированно не совпадает с this, НЕ null и имеет нужный класс
    protected boolean isEqual(Foo that) {
        return bar == that.bar && Objects.equals(baz, that.baz);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bar, baz);
    }
}

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


Более того, в некоторых случаях (например, когда у класса Foo нет наследников) допустимо использовать классическую проверку при помощи instanceof - тогда можно убрать проверку на null:

public final class Foo {  // наследников не будет
    // ...

    @Override
    public boolean equals(Object that) {
        return this == that || (that instanceof Foo && isEqual((Foo) that));
    }

    // ...
}

В таком случае важно, чтобы обеспечивалась симметричность при сравнении x.equals(y) == y.equals(x).


Есть также несколько замечаний по поводу реализации сравнения в классе MyClass в вопросе:

double a;
Object b;
  • Сравнивать double при помощи == можно только для литералов, в общем же случае такой способ не рекомендуется; если хотя бы одно из чисел вычисляется, сравнение должно выполняться с некоторой заданной точностью
  • Для сравнения полей b как раз и нужно использовать стандартный метод Objects.equals
▲ 0

Первый return (if (this == o) return true;) не нужен. Во всех учебниках он есть. Он экономит время если объект сравнивается сам с собой. И он тратит время зря, если сравниваются два разных объекта. Можно показать, что суммарно времени тратится больше чем выигрывается. Этот оператор можно и нужно убрать.

Второй return (if (o == null) return false;) необходим, так как equals обязан правильно обрабатывать нулевые ссылки. Если не будет этого оператора, нулевая ссылка o приведёт к исключению дальше.

Третий return (if (getClass() != o.getClass()) return false;) нужен чтобы объекты разных типов не оказались равны друг-другу. Можно вообразить ситуацию когда равенство объектов разных типов полезно, но это очень специальная ситуация. Если этой проверки не будет, то приведение из Object в MyClass ниже по коду может выбросить исключение.

Четвёртый return выполняет всю полезную работу по сравнению объектов.

Требования к реализации можно найти в документации по Object.equals.

P.S. Можно считать что второй и третий операторы проверяют равенство типов. Четвёртый проверяет равенство значений.