4

I've been tasked with this:

Override the equals() and hashCode() methods appropriately in your Rational Numbers class.

It further expands on this. My question is, when you override the equals method, do you change the parameter passed in? If it checks for logical equivalence then I've done the following:

public boolean equals(Rational rhs){
    if(this.num == rhs.num && this.denom == rhs.denom){
        return true;
    }
    return false;
}

I'm unsure if this is the way to go when overriding the method. If it is, then when you override the hashcode method, is it just a simple job of picking a good piece of code to assign hashcodes?

Additionally, this is the hashcode I've created. Is this along the right lines?

@Override
public int hashCode(){
    int hc = 17;
    hc = 37 * hc + ((num == 0 && denom == 0) ? 0 : num);
    return 37 * hc + denom;


    //boolean b = cond ? boolExpr1 : boolExpr2;
    //if cond true then b=boolExpr1 else b=boolExpr2
}
Community
  • 1
  • 1
Andrew Coates
  • 119
  • 2
  • 12
  • 4
    This is an overload of equals, not an override. In order to correctly implement equals, it MUST get a type Object as parameter – Stultuske Mar 08 '16 at 10:06

3 Answers3

9

Your equals method doesn't override Object's equals. In order to override it, the argument must be an Object.

@Override
public boolean equals(Object other){
    if (!(other instanceof Rational))
        return false;
    Rational rhs = (Rational) other;
    if(this.num == rhs.num && this.denom == rhs.denom){
        return true;
    }
    return false;
}

Note that the @Override annotation helps you detect cases in which you meant to override a method but overloaded it instead. If you put that annotation in your original method, you'll get a compilation error, since your method is not overriding any method (assuming your Rational class doesn't extend or implement some class/interface that contains a boolean equals(Rational rhs) method).

Eran
  • 387,369
  • 54
  • 702
  • 768
5

You can do this however as this doesn't override equals(Object) you still need to do this

public boolean equals(Object o) {
    return o instanceof Rational && equals((Rational) o);
}

The reason equals(Object) is called is you might have a collection of many different types.

List list = Arrays.asList(1, "Hello", new Rational(1, 2));
// needs to be able to compare with 1 and "Hello"
list.contains(new Rational(1, 2)); 
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
0

To correctly override a method - to have your override method called "magically" when the method is called against a reference of the super-class type - your argument types must be exactly the same as those in the superclass.

To see why this must be the case, consider what would happen here:

Object r = new Rational();
boolean b = r.equals("x");

We have created an Object reference to a Rational, which we are allowed to do, and we are passing a String to the Object.equals(Object) method, which we are also allowed to do, because a String is an Object.

If your override had worked, you'd now have a Rational reference to a String object - madness! That would break the type-safety rules in Java. By forcing your args to be an exact match, the language can ensure that only "acceptable" arguments appear in the method.

Of course, to make your method work, you'll now need a cast to explicitly consider the 'obj' arg to be a Rational: Rational rhs = (Rational)obj; - and probably you'll have wanted to perform an instanceof check beforehand to ensure that obj actually is a Rational, and not some other type (like a String!).

if (obj instanceof Rational) {
    Rational r = (Rational)obj;
    ....

Some issues worth understanding:

  • In the case above where we call r.equals("x"): you might think "can't the compiler just see that r is really pointing at a Rational"? And the answer is "yes": Java just chooses not to. Fact is, this is a simplistic example for demonstrating the point. Generally, the rules on reference typing are quite strict in order to be consistent, and usually, the compiler can't "see" situations like this - like if the Rational was being passed into a library method as an Object reference, or was a member of an Object collection.
  • The equals method has a moderately complex contract, and it's worth studying: you should be checking for null, worrying about what would happen with subclasses etc. My use of instanceof above is controversial - getClass() == obj.getClass() might be better. Look it up - Joshua Bloch's Effective Java is great on this subject. Or read this: What issues should be considered when overriding equals and hashCode in Java?
  • There is a theoretical mechanism, which Java does not support, called "contravariant parameter overrides". This would allow myMethod(String) to be overridden by myMethod(Object) (ie you can use a more general argument - opposite to your case) which is always safe and doesn't break the rules. But it causes problems of its own, and Java doesn't support it; see this answer here: Why is there no parameter contra-variance for overriding?

Your final question about implementing a hashCode() method: in a simple class like this, yes, a generic hashcode method should be fine. Usually, getting the equals bit right is 95% of the work.

Edit: In your updated question, you supply a hashCode method which does ... quite a lot of thinking! You could strip it down to just return (31 + num) * 31 + denom;, or similar. I use 31 because of this:Why does Java's hashCode() in String use 31 as a multiplier?.

Community
  • 1
  • 1
SusanW
  • 1,550
  • 1
  • 12
  • 22