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?.