Re: If the object is an instance of a class, why was it cast?
On Jul 5, 11:51 pm, metaperl <metap...@gmail.com> wrote:
re:http://java.sun.com/docs/books/tutorial/java/IandI/objectclass.html
// why cast obj if it is an instance of Book?
// if it is an instance of Book, then it must have the method
available to it,
// either directly in its class or via dispatch to its parent classes
public boolean equals(Object obj) {
if (obj instanceof Book)
return ISBN.equals((Book)obj.getISBN());
else
return false;
}
Because javac is stone dumb. It would really be nice if it obviated
the need for such casts inside if branches based on instanceof and
wherever else the object's run-time type is easily shown by static
analysis to be more specific than the reference's type. Unfortunately,
the compiler has roughly the IQ of a turnip, so it still thinks obj
might be a String or who-knows-what when compiling that line.
Actually, it would be nice if there was a way to avoid common
boilerplate code in equals methods. Say specifying an equals method in
a class without "abstract" or a body made it generate an equals
equivalent to
public boolean equals (Object obj) {
if (obj == this) return true;
if (obj == null) return false;
if (!obj instanceof WhateverClass) return false;
WhateverClass wc = (WhateverClass)obj;
if (wc.fieldOne != fieldOne && (fieldOne == null || wc.fieldOne ==
null || !fieldOne.equals(wc.fieldOne))) return false;
if (wc.fieldTwo != fieldTwo && (fieldTwo == null || wc.fieldTwo ==
null || !fieldTwo.equals(wc.fieldTwo))) return false;
...
return true;
}
Of course, it might reduce some of those to if (wc.fieldThree !=
fieldThree) for a field type that's final or private and doesn't
override Object's equals(). It would ignore transient fields and use
all the others. It might do this only if the equals() being overridden
is Object's, and otherwise use
public boolean equals (Object obj) {
if (obj == this) return true;
if (obj == null) return false;
if (!super.equals(obj)) return false;
if (!obj instanceof WhateverClass) return false;
WhateverClass wc = (WhateverClass)obj;
if (wc.fieldOne != fieldOne && (fieldOne == null || wc.fieldOne ==
null || !fieldOne.equals(wc.fieldOne))) return false;
if (wc.fieldTwo != fieldTwo && (fieldTwo == null || wc.fieldTwo ==
null || !fieldTwo.equals(wc.fieldTwo))) return false;
...
return true;
}
using the superclass equals and then comparing the nontransient fields
specific to the subclass.
And of course you'd want to be able to autogenerate hashCode the same
way, getting some reasonable thing using all the nontransient fields
(and when super.hashCode() isn't Object.hashCode(), super.hashCode())
to generate the hash, multiplying each field's hashCode() by a random
value (hash of the field's name?) and summing the results.
This gives reasonable and correct equals and hashCode behavior for a
large chunk of the likely cases. The rest can be overridden in the
existing way. Another option, probably cleaner if less powerful, is
just to have one new methods in Object:
@SuppressWarnings("unchecked")
protected final boolean <T> typicalEquals (Object obj, Class<T> base)
{
if (this == obj) return true;
if (obj == null) return false;
if (!base.isAssignableFrom(obj.getClass())) return false;
return ((EqualityComparable<? super T>)this).equalTo((T)obj);
}
Add this interface:
public interface EqualityComparable <T> {
boolean equalTo (T obj);
}
A subclass Foo that overrides Object's equals can then implement
EqualityComparable<Foo> and write:
public Object equals (Object obj) {
return typicalEquals(obj, Foo.class);
}
public boolean equalTo (Foo obj) {
if (!obj.getClass().equals(Foo.class)) return obj.equalTo(this);
if (obj.fieldOne != fieldOne && (fieldOne == null || obj.fieldOne ==
null || fieldOne.equals(obj.fieldOne))) return false;
if (obj.intField != intField) return false;
if (!obj.neverNullField.equals(neverNullField) return false;
return true;
}
with the class-specific guts of the equals test in equalTo.
Then its own subclasses can just override equalTo, and possibly use
"if (!super.equalTo(obj)) return false;" when appropriate.
The effect of the above is that Foo is unequal to null or to any non-
Foo object. It is equal to any Foo it is identical to. It is also
equal to any Foo for which equalTo(Foo) returns true, but to no other
Foos.
The line
if (!obj.getClass().equals(Foo.class)) return obj.equalTo(this);
means that if a vanilla Foo (or one that doesn't override Foo's
equalTo) is compared to a subclass instance the subclass equalTo is
used instead. Of course this is an infinite recursion if two Foo
subclasses both fail to override equalTo. This might not be a good
idea depending -- you might want real double dispatch, or for Foo to
know how to compare two Foos regardless of subclass, even subclasses
not known of by the writer of Foo. Whether this is feasible depends on
what Foo really is, and these same issues arise with writing equals()
methods already.
And obviously you want Foo's hashCode() to be consistent with the
equalTo() method here...
ClassCastException can be thrown for three reasons here. One, if it's
thrown in equalTo for any reason. Two, if you call typicalEquals
without implementing EqualityComparable. And three, if you call
typicalEquals(obj, SomeClass.class) in a class that doesn't implement
EqualityComparable<AnotherClass> where AnotherClass is, or is a
supertype of, SomeClass. (The object obj is silently cast to
AnotherClass when equalTo(AnotherClass) is called with it as argument,
due to the unchecked cast of obj to T. Since obj is only sure to be of
SomeClass, though this is assured by getting past the if(! ...
assignableFrom ...) return false line, this means SomeClass has to be
assignable to AnotherClass. If that isn't the case, essentially the
error is that "this" is not in fact assignable to EqualityComparable<?
extends T> when cast to same inside typicalEquals.)
NullPointerException should only be thrown if your equalTo method
throws it.
Disclaimer: the above hasn't really been tested in any way and might
require slight adjustment, but the basic premise looks sound.