Java — абстрактный класс, equals() и два подкласса

У меня есть абстрактный класс с именем Xpto и два расширяющих его подкласса с именами Person и Car. У меня также есть класс с именем Test с функцией main() и методом foo(), который проверяет, являются ли два человека или машины (или любой объект класса, расширяющего Xpto). равно. Таким образом, я переопределил equals() в классах Person и Car. Два человека равны, если у них одно и то же имя, и два автомобиля равны, если у них одинаковая регистрация.

Однако, когда я вызываю foo() в классе Test, я всегда получаю "false". Я понимаю, почему: equals() не переопределяется в абстрактном классе Xpto. Итак... как я могу сравнить двух людей или автомобили (или любой объект класса, расширяющего Xpto) в этом методе foo()?

В общем, это код, который у меня есть:

public  abstract class Xpto {


}

public class Person extends Xpto{

        protected String name;

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

        public boolean equals(Person p){
                System.out.println("Person equals()?");
                return this.name.compareTo(p.name) == 0 ? true : false;
        }
}

public class Car extends Xpto{
        protected String registration;

        public Car(String registration){
                this.registration = registration;
        }

        public boolean equals(Car car){
                System.out.println("Car equals()?");
                return this.registration.compareTo(car.registration) == 0 ? true : false;
        }
}

public class Teste {

        public static void foo(Xpto xpto1, Xpto xpto2){
                if(xpto1.equals(xpto2))
                        System.out.println("xpto1.equals(xpto2) -> true");
                else
                        System.out.println("xpto1.equals(xpto2) -> false");

        }

        public static void main(String argv[]){
                Car c1 = new Car("ABC");
                Car c2 = new Car("DEF");
                Person p1 = new Person("Manel");
                Person p2 = new Person("Manel");

                foo(p1,p2);
        }
}

person msr    schedule 23.04.2010    source источник


Ответы (9)


Как говорят другие, сигнатура метода, который вы переопределяете, должна быть точно такой же. При переопределении методов, чтобы убедиться, что вы переопределяете, используйте аннотацию @Override над функцией, чтобы такие IDE, как Eclipse, предупредили вас, если вы изменили метод.

Вот как это будет выглядеть:

@Override
public boolean equals(Object obj){
...Your code here...
}

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

Таким образом, ваш окончательный код будет:

@Override
public boolean equals(Object obj){
...Your code here...
}

@Override
public int hashCode(){
...Your code here...
}

Дополнительную информацию можно найти в javadoc.

person pakore    schedule 23.04.2010
comment
Ответ номер 11 перед упоминанием @Override. вздыхает +1 - person Tom Hawtin - tackline; 23.04.2010

Я понимаю, почему: equals() не переопределяется в абстрактном классе Xpto.

На самом деле equals() не переопределяется где-либо в вашем коде. Чтобы переопределить его, ваш метод должен иметь Object в качестве типа параметра, и вы должны привести его (после тестирования с instanceof, чтобы вернуть false при сравнении экземпляров двух разных подклассов).

person Michael Borgwardt    schedule 23.04.2010

объявление общедоступного логического равенства (Person p) или общедоступного логического равенства (Car p) не отменяет общедоступное логическое равенство Object (Object o), это просто новый метод, который никогда не вызывается.

person Maurice Perry    schedule 23.04.2010

Вот как бы я поступил:

public  abstract class Xpto {

}

public class Person extends Xpto{

    protected String name;

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

    public boolean equals(Object o){
       if(o == null || !getClass().equals(o.getClass())
          return false;
       Person p = (Person) o;
       System.out.println("Person equals()?");
       return this.name.compareTo(p.name) == 0 ? true : false;
    }
}

public class Car extends Xpto {
    protected String registration;

    public Car(String registration){
            this.registration = registration;
    }

    public boolean equals(Object o){
       if(o == null || !getClass().equals(o.getClass())
          return false;
       Car car = (Car) o;
       System.out.println("Car equals()?");
       return this.registration.compareTo(car.registration) == 0 ? true : false;
    }
}

public class Teste {

    public static void foo(Xpto xpto1, Xpto xpto2){
            if(xpto1.equals(xpto2))
                    System.out.println("xpto1.equals(xpto2) -> true");
            else
                    System.out.println("xpto1.equals(xpto2) -> false");

    }

    public static void main(String argv[]){
            Car c1 = new Car("ABC");
            Car c2 = new Car("DEF");
            Person p1 = new Person("Manel");
            Person p2 = new Person("Manel");

            foo(p1,p2);
    }
}

Каждый класс наследует метод equals(Object) от класса Object. Таким образом, Xpto не нужно определять такой метод.

Когда кто-то переопределяет этот метод в подклассах (а именно: Person, Car), его необходимо определить с точно такой же сигнатурой. Другими словами, параметр метода equals должен иметь тип Object, и реализация метода должна приводить его к понижению.

person Itay Maman    schedule 23.04.2010
comment
getClass().equals(o.getClass()) является ключевым условием для абстрактного класса. - person Erikson Rodriguez; 23.04.2021

Javadoc утверждает, что вам нужно переопределить метод equals с объектом в качестве параметра.

Указывает, является ли какой-либо другой объект "равным" этому.

Поэтому ваши методы equals подклассов должны выглядеть примерно так:

public class Car extends Xpto
{
    protected String registration;

    public Car(String registration)
    {
        this.registration = registration;
    }

    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (obj == this)
        {
            return true;
        }
        if (!obj.getClass().isAssignableFrom(getClass()))
        {
            return false;
        }
        Car car = (Car) obj;
        return this.registration.compareTo(car.registration) == 0 ? true : false;
    }
}
person Daff    schedule 23.04.2010

Как правило, очень сложно/невозможно полностью выполнить контракт equals и при этом иметь два разных класса в иерархии, равных друг другу, и обычно это не делается. Как правило, метод equals проверяет, является ли класс одинаковым (поэтому два экземпляра одного и того же подкласса будут равны друг другу, а два экземпляра двух разных подклассов - нет).

Однако в вашем случае можно реализовать equals в Xpto, поскольку есть только одно свойство. Очевидный способ сделать это — определить абстрактный метод в Xpto, а затем также переопределить equals в Xpto:

 public class Xpto {
        protected abstract String getIdentity();

        @Override
        public boolean equals(Object o) {
            if (o == null) return false;
            //Typical implementation
            //if (getClass() != o.getClass()) return false;
            if (!(o instanceof Xpto)) return false; //risky implementation, but will allow a car to compare to a person
             return getIdentity().equals((Xpto) o.getIdentity());
        }

        @Override
        public int hashCode() {
             return getIdentity().hashCode();
        }
  }

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

person Yishai    schedule 23.04.2010
comment
Вау, мы опубликовали одно и то же решение одновременно: P - person pakore; 23.04.2010

Ваш метод equals должен выглядеть так:

@Override public boolean equals(Object o) {
   if (!(o instanceof YourType)) {
      return false;
   }
   YourType yt = (YourType)o;
   ... // rest here
}

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

person JRL    schedule 23.04.2010

Вы не переопределяете метод equals(), а перегружаете его. Изменить подпись на

public boolean equals(Object o)

А затем примените к Человеку/Автомобилю и сравните.

Кстати, вы также можете сравнить строки с equals():

return registration.equals(car.registration);
person fish    schedule 23.04.2010

Ваши подклассы определяют equals(Person) или equals(Car), ни одному из которых не понравится передача Xpto. Если вы объявите их обоих равными (Xpto) или, что еще лучше, равными (Object), чтобы они работали в коллекциях, тогда ваша проблема должна исчезнуть.

Обратите внимание: если вы повторно объявите методы equals() таким образом, (1) вам нужно будет проверить классы объектов, которые вы передаете, поскольку вы больше не можете гарантировать, что они являются автомобилями или людьми, и (2) вы вероятно, вы также захотите переопределить getHashCode(), особенно если вы решите сделать их обоих равными (Object), потому что getHashCode() должен возвращать одинаковые хэш-коды для двух равных объектов.

person cHao    schedule 23.04.2010