Как переопределить метод equals в Java

108

Я пытаюсь переопределить метод equals в Java. У меня есть класс, Peopleкоторый в основном имеет 2 поля данных nameи age. Теперь я хочу переопределить equalsметод, чтобы я мог проверять между двумя объектами People.

Мой код выглядит следующим образом

public boolean equals(People other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(other.name) &&  age.equals(other.age);
    } // end else

    return result;
} // end equals

Но когда я пишу, age.equals(other.age)это дает мне ошибку, поскольку метод equals может сравнивать только String, а age - Integer.

Решение

Я использовал предложенный ==оператор, и моя проблема решена.

бобр
источник
3
Эй, как насчет this.age == other.age? :)
denis.solonenko
1
Какой тип данных для возраста? int ИЛИ Целое число? Кроме того, какую версию JDK вы используете?
Manish
2
«как метод equals может сравнивать только String» - Кто вам сказал, что метод equals может сравнивать только String? Метод equals принадлежит к классу Object, и любой созданный класс будет иметь реализацию equals по умолчанию. Вы можете призывать равных на ЛЮБОМ классе Java
Manish

Ответы:

128
//Written by K@stackoverflow
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        ArrayList<Person> people = new ArrayList<Person>();
        people.add(new Person("Subash Adhikari", 28));
        people.add(new Person("K", 28));
        people.add(new Person("StackOverflow", 4));
        people.add(new Person("Subash Adhikari", 28));

        for (int i = 0; i < people.size() - 1; i++) {
            for (int y = i + 1; y <= people.size() - 1; y++) {
                boolean check = people.get(i).equals(people.get(y));

                System.out.println("-- " + people.get(i).getName() + " - VS - " + people.get(y).getName());
                System.out.println(check);
            }
        }
    }
}

//written by K@stackoverflow
public class Person {
    private String name;
    private int age;

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

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (obj.getClass() != this.getClass()) {
            return false;
        }

        final Person other = (Person) obj;
        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
            return false;
        }

        if (this.age != other.age) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
        hash = 53 * hash + this.age;
        return hash;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Вывод:

бегать:

- Субаш Адхикари - VS - K ложь

- Субаш Адхикари - VS - StackOverflow false

- Субаш Адхикари - VS - Субаш Адхикари верно

- K - VS - StackOverflow false

- K - VS - Субаш Адхикари ложь

- StackOverflow - VS - Subash Adhikari false

- СТРОИТЬ УСПЕШНО (общее время: 0 секунд)

Ким
источник
7
что hash = 53 * hashпочему вы используете это?
kittu
2
Использование getClass()вызовет проблемы, если класс станет подклассом и будет сравниваться с объектом суперкласса.
Tuxdude
1
может быть bcoz 53 - простое число , взгляните на этот ответ stackoverflow.com/a/27609/3425489 , - прокомментировал он, выбирая числа вhashCode()
Shantaram
1
Победивший ответ на этот вопрос дает отличное объяснение того, почему вы переопределяете hashCode () stackoverflow.com/a/27609/1992108
Pegasaurus
7
Рассмотрите возможность использования if (getClass ()! = Obj.getClass ()) ... вместо использования instanceofоператора или isAssignableFrom. Для этого потребуется точное соответствие типа, а не соответствие подтипа. - Симметричное требование. Также для сравнения Stringили других типов объектов вы можете использовать Objects.equals(this.name,other.name).
YoYo
22

Введение новой сигнатуры метода, изменяющей типы параметров, называется перегрузкой :

public boolean equals(People other){

Здесь Peopleотличается от Object.

Когда сигнатура метода остается идентичной сигнатуре его суперкласса, это называется переопределением, и @Overrideаннотация помогает различать их во время компиляции:

@Override
public boolean equals(Object other){

Не видя фактического объявления age, трудно сказать, почему возникает ошибка.

Фортран
источник
18

Я не уверен в деталях, поскольку вы не опубликовали весь код, но:

  • не забудьте отменить hashCode(), а
  • equalsметод должен иметь Object, а не в Peopleкачестве типа аргумента. В настоящий момент вы перегружаете, а не переопределяете метод equals, что, вероятно, не то, что вам нужно, особенно с учетом того, что вы проверите его тип позже.
  • вы можете использовать, instanceofчтобы проверить, что это объект People, напримерif (!(other instanceof People)) { result = false;}
  • equalsиспользуется для всех объектов, но не для примитивов. Я думаю, вы имеете в виду возраст int(примитив), и в этом случае просто используйте ==. Обратите внимание, что целое число (с большой буквы) - это объект, который следует сравнивать с равным.

См. Какие вопросы следует учитывать при переопределении equals и hashCode в Java? Больше подробностей.

Адриан Муат
источник
12
@Override
public boolean equals(Object that){
  if(this == that) return true;//if both of them points the same address in memory

  if(!(that instanceof People)) return false; // if "that" is not a People or a childclass

  People thatPeople = (People)that; // than we can cast it to People safely

  return this.name.equals(thatPeople.name) && this.age == thatPeople.age;// if they have the same name and same age, then the 2 objects are equal unless they're pointing to different memory adresses
}
NeverJr
источник
12

Пункт 10: Соблюдайте общий договор при переопределении равных

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

  • Каждый экземпляр класса уникален по своей сути . Это верно для таких классов, как Thread, которые представляют активные сущности, а не значения. Реализация equals, предоставляемая Object, имеет в точности правильное поведение для этих классов.

  • Классу нет необходимости предоставлять тест на «логическое равенство». Например, java.util.regex.Pattern мог бы переопределить equals, чтобы проверить, представляют ли два экземпляра Pattern одно и то же регулярное выражение, но дизайнеры не думали, что клиентам понадобится или захотят эта функциональность. В этих условиях реализация equals, унаследованная от Object, является идеальной.

  • Суперкласс уже переопределил равенство, и поведение суперкласса подходит для этого класса. Например, большинство реализаций Set наследуют свою реализацию equals от AbstractSet, реализации List от AbstractList и реализации Map от AbstractMap.

  • Класс является частным или частным для пакета , и вы уверены, что его метод equals никогда не будет вызван. Если вы крайне не склонны к риску, вы можете переопределить метод equals, чтобы гарантировать, что он не будет вызван случайно:

equalsМетод реализует отношение эквивалентности. Он имеет следующие свойства:

  • Рефлексивный: Для любого эталонного значения ненулевого x, x.equals(x)должно возвращать верно.

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

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

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

  • Для любого эталонного значения ненулевым x, x.equals(null)должен вернуться false.

Вот рецепт высококачественного метода равенства:

  1. Используйте ==оператор, чтобы проверить, является ли аргумент ссылкой на этот объект. Если да, верните true. Это всего лишь оптимизация производительности, но ее стоит сделать, если сравнение потенциально дорого.

  2. Используйте instanceofоператор, чтобы проверить, имеет ли аргумент правильный тип. Если нет, верните false. Как правило, правильный тип - это класс, в котором используется метод. Иногда этот класс реализует какой-то интерфейс. Используйте интерфейс, если класс реализует интерфейс, который уточняет контракт равенства, чтобы разрешить сравнения между классами, реализующими интерфейс. Это свойство есть у интерфейсов коллекций, таких как Set, List, Map и Map.Entry.

  3. Приведите аргумент к правильному типу. Поскольку этому приведению предшествовал тест instanceof, он гарантированно завершится успешно.

  4. Для каждого «значимого» поля в классе проверьте, соответствует ли это поле аргумента соответствующему полю этого объекта. Если все эти тесты прошли успешно, верните true; в противном случае верните false. Если тип на шаге 2 является интерфейсом, вы должны получить доступ к полям аргумента через методы интерфейса; если тип является классом, вы можете получить доступ к полям напрямую, в зависимости от их доступности.

  5. Для примитивных полей, тип которых не равен floatили double, используйте ==оператор для сравнения; для полей ссылки на объект вызовите equalsметод рекурсивно; для floatполей используйте статический Float.compare(float, float)метод; а для doubleполей используйте Double.compare(double, double). Специальная обработка поплавка и двойных полей стало необходимым существованием Float.NaN, -0.0fи аналогичных двойных значений; В то время как вы могли бы сравнить floatи doubleполя со статическими методами Float.equalsи Double.equalsэто повлекло бы за собой Autoboxing на каждое сравнение, который будет иметь низкую производительность. Для arrayполей примените эти рекомендации к каждому элементу. Если каждый элемент в поле массива имеет значение, используйте один из Arrays.equalsметодов.

  6. Некоторые поля ссылки на объект могут законно содержать null. Чтобы избежать вероятности возникновения ошибки NullPointerException, проверьте такие поля на равенство с помощью статического метода Objects.equals(Object, Object).

    // Class with a typical equals method
    
    public final class PhoneNumber {
    
        private final short areaCode, prefix, lineNum;
    
        public PhoneNumber(int areaCode, int prefix, int lineNum) {
    
            this.areaCode = rangeCheck(areaCode,  999, "area code");
    
            this.prefix   = rangeCheck(prefix,    999, "prefix");
    
            this.lineNum  = rangeCheck(lineNum,  9999, "line num");
    
        }
    
        private static short rangeCheck(int val, int max, String arg) {
    
            if (val < 0 || val > max)
    
               throw new IllegalArgumentException(arg + ": " + val);
    
            return (short) val;
    
        }
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNum == lineNum && pn.prefix == prefix
                    && pn.areaCode == areaCode;
        }
        ... // Remainder omitted
    
    }
    

источник
1
Не забудьте упомянуть, что вам также нужно переопределить hashCode(). Также обратите внимание, что с момента написания Java7 equals()и hashCode()методов стало намного проще с помощью Objects.equals(), Arrays.equals()и Objects.hashCode(), Arrays.hashCode().
Арнольд Шрайвер
3
Рассмотрите возможность использования, if (getClass() != obj.getClass()) ...а не использования оператора instanceof. Для этого потребуется точное соответствие типа, а не соответствие подтипа. - Симметричное требование.
YoYo
@YoYo правильно ... использование instanceof может привести к сбою симметричного свойства. Если o является подклассом PhoneNumber, например, PhoneNumberWithExtension, и он переопределяет equals таким же образом, используя instanceof, тогда o.equals (this) не пройдёт тест instanceof, а PhoneNumber.equals передаст его и вернет true (при условии, что все остальные поля PhoneNumber равны).
ldkronos
5

Поскольку я предполагаю, что ageэто тип int:

public boolean equals(Object other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(otherPeople.name) &&  age == otherPeople.age;
    } // end else

    return result;
} // end equals
Лучиан Григоре
источник
Это приведет к NullPointerExceptionif nameis null.
orien
@orien Ничего страшного, может быть, это в контракте, которому nameникогда не присваивается nullзначение ...
fortran
@fortran Итак ... может, это не имеет большого значения;)
orien
5

При сравнении объектов в Java вы выполняете семантическую проверку , сравнивая тип и идентифицирующее состояние объектов с:

  • сам (тот же экземпляр)
  • сам (клон или реконструированная копия)
  • другие объекты разных типов
  • другие объекты того же типа
  • null

Правила:

  • Симметрия :a.equals(b) == b.equals(a)
  • equals()всегда дает trueили false, но никогда NullpointerException, ClassCastExceptionили любой другой бросаемый

Сравнение:

  • Проверка типа : оба экземпляра должны быть одного типа, что означает, что вам нужно сравнить фактические классы на предмет равенства. Это часто неправильно реализуется, когда разработчики используют instanceofдля сравнения типов (что работает только до тех пор, пока нет подклассов, и нарушает правило симметрии, когда A extends B -> a instanceof b != b instanceof a).
  • Семантическая проверка состояния идентификации : убедитесь, что вы понимаете, по какому состоянию идентифицируются экземпляры. Человека можно идентифицировать по номеру социального страхования, но не по цвету волос (можно покрасить), имени (можно изменить) или возрасту (все время меняется). Только с объектами значений следует сравнивать полное состояние (все непереходные поля), в противном случае проверяйте только то, что идентифицирует экземпляр.

Для вашего Personкласса:

public boolean equals(Object obj) {

    // same instance
    if (obj == this) {
        return true;
    }
    // null
    if (obj == null) {
        return false;
    }
    // type
    if (!getClass().equals(obj.getClass())) {
        return false;
    }
    // cast and compare state
    Person other = (Person) obj;
    return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}

Универсальный универсальный служебный класс многократного использования:

public final class Equals {

    private Equals() {
        // private constructor, no instances allowed
    }

    /**
     * Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
     *
     * @param instance       object instance (where the equals() is implemented)
     * @param other          other instance to compare to
     * @param stateAccessors stateAccessors for state to compare, optional
     * @param <T>            instance type
     * @return true when equals, false otherwise
     */
    public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
        if (instance == null) {
            return other == null;
        }
        if (instance == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (!instance.getClass().equals(other.getClass())) {
            return false;
        }
        if (stateAccessors == null) {
            return true;
        }
        return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
    }
}

Для вашего Personкласса, используя этот служебный класс:

public boolean equals(Object obj) {
    return Equals.as(this, obj, t -> t.name, t -> t.age);
}
Питер Вальзер
источник
1

если age является int, вы должны использовать ==, если это объект Integer, вы можете использовать equals (). Вам также необходимо реализовать метод хэш-кода, если вы переопределите equals. Подробная информация о контракте доступна в javadoc Object, а также на различных веб-страницах.

Эшвайни К Джа
источник
0

Вот решение, которое я недавно использовал:

public class Test {
    public String a;
    public long b;
    public Date c;
    public String d;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Test)) {
            return false;
        }
        Test testOther = (Test) obj;
        return (a != null ? a.equals(testOther.a) : testOther.a == null)
                && (b == testOther.b)
                && (c != null ? c.equals(testOther.c) : testOther.c == null)
                && (d != null ? d.equals(testOther.d) : testOther.d == null);
    }

}
SSharma
источник