views:

395

answers:

10

Hello,

I have an abstract class named Xpto and two subclasses that extend it named Person and Car. I have also a class named Test with main() and a method foo() that verifies if two persons or cars (or any object of a class that extends Xpto) are equals. Thus, I redefined equals() in both Person and Car classes. Two persons are equal when they have the same name and two cars are equal when they have the same registration.

However, when I call foo() in the Test class I always get "false". I understand why: the equals() is not redefined in Xpto abstract class. So... how can I compare two persons or cars (or any object of a class that extends Xpto) in that foo() method?

In summary, this is the code I have:

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);
        }
}
+1  A: 

Don't you need public boolean equals(Object o) as the method signature in both of your classes?

Matthew Wilson
+1  A: 

The Javadoc states that you need to override the equals method with object as a parameter.

Indicates whether some other object is "equal to" this one.

Therefore your subclasses equals methods should look something like this:

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;
    }
}
Daff
+2  A: 

I understand why: the equals() is not redefined in Xpto abstract class.

Actually equals() isn't redefined anywhere in your code. To override it, your method has to have Object as parameter type and you have to cast it (after testing with instanceof to return false when instances of two different subclasses are compared).

Michael Borgwardt
+1  A: 

declaring public boolean equals(Person p) or public boolean equals(Car p) does not override Object's public boolean equals(Object o), it's just a new method that is never called.

Maurice Perry
A: 

Here's how I would go about it:

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);
    }
}

Every class inherit an equals(Object) method from the Object class. Thus, Xpto does not need to define such a method.

When one overrides this method in subclasses (namely: Person, Car) one must define it with the exact same signature. In other words, the parameter of the equals method must be of type Object, and the method implementation must downcast it.

Itay
+1  A: 

Your equals method should look like:

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

Also, don't forget to override hashCode as well, to be able to properly use your types in collections.

JRL
A: 

You are not overriding the equals() method, instead you overload it. Change the signature to

public boolean equals(Object o)

And then cast o to Person/Car and do the comparison.

And BTW, you could compare the strings with equals() as well:

return registration.equals(car.registration);
fish
A: 

Your subclasses are defining equals(Person) or equals(Car), neither of which is going to like being passed an Xpto. If you declare them both as equals(Xpto), or better yet equals(Object) so that they'll work in collections, then your problem should go away.

Note, if you redeclare the equals() methods this way, (1) you'll need to check the classes of the objects you get passed, since you can't guarantee they're Cars or Persons anymore, and (2) you'll probably want to override getHashCode() as well, especially if you decide to make them both equals(Object), cause getHashCode() should return equal hash codes for two objects that are equal.

cHao
+1  A: 

It is generally very difficult/impossible to fully fulfill the equals contract and still have two different classes in the hierarchy equal to each other, and it is generally not done. Generally an equals method tests for the class being the same (so two instances of the same subclass will equal each other, but two instances of two different subclasses will not).

However, in your case it is possible to implement an equals in Xpto since there is only one property. The obvious way to do this is to define an abstract method in Xpto, and then override equals in Xpto as well:

 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();
        }
  }

Others have pointed out that you did not actually override equals in your implementation. In the future you can get the compiler to help you out with that by using the @Override annotation. In your case you would have gotten a compile error early which would have saved you some time.

Yishai
Wow we posted the same solution at the same time :P
pakore
+2  A: 

As the others say, the signature of the method you override must be exactly the same. When overriding methods, to make sure you are overriding, use the @Override annotation above the function, so IDEs like Eclipse will warn you if you changed the method.

This is what it would look like:

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

I would suggest to override hashCode() as well because when inserting items into lists, sets, hastables, etc... for equality (and performande) hashCode() is used (and sometimes equals() is not!)

So your final code would be:

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

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

More info at the javadoc

pakore
Answer number 11 before `@Override` is mentioned. *sigh* +1
Tom Hawtin - tackline