If the object is an instance of a class, why was it cast?

T

Twisted

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

bencoe

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.

I think that this would make things quite a bit more confusing, if I
had to modify my coding style to take into account the fact that I'm
in, for instance, an "instanceof" block. I'd much rather go to the
trouble of habitually casting things -- this really isn't very much
trouble.

In answer to the original question: you have to cast it because, like
Twisted says, the runtime environment is currently only aware that it
has been passed something of the superclass Object -- giving it access
only to the following methods until you cast,

http://java.sun.com/j2se/1.3/docs/api/java/lang/Object.html
 
M

Manivannan Palanichamy

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

(Book)obj.getISBN() -- this expression can be rewritten like,
Book tmp = (Book)obj;
tmp.getISBN(); the compiler is very sure that, tmp is an instance of
Book and it has the method getISBN(), so the compilation succeeds.

But, if you dont cast it, and just make the statement like,
obj.getISBN() -- the compiler does not find the getISBN() method in
Object class, and simply blasts. Its doing it's job very well
actually.

Thing is, the poor compiler cannot predict, which object, the
reference can point to, at run time. Thats why you need a cast. But,
this hurdle is removed in Java 1.5. Yes, read about Generics of Java
1.5.
 
R

Roedy Green

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

see http://mindprod.com/jgloss/cast.html
 
L

Lew

I think that this would make things quite a bit more confusing, if I
had to modify my coding style to take into account the fact that I'm
in, for instance, an "instanceof" block. I'd much rather go to the
trouble of habitually casting things -- this really isn't very much
trouble.

In answer to the original question: you have to cast it because, like
Twisted says, the runtime environment is currently only aware that it
has been passed something of the superclass Object -- giving it access
only to the following methods until you cast,

http://java.sun.com/j2se/1.3/docs/api/java/lang/Object.html

This is a slight misstatement of the situation. Java distinguishes between
the variable, whose compile-time type only is known, and the referenced
object, whose full run-time type is known. This has nothing to do with "javac
.... having the IQ of a turnip" but is a deliberate Java feature to support
disciplined coding and the idioms of polymorphism.

The variable of "Object" type can only refer to methods known to the "Object"
type because that is the type of the variable. Turnip-brained or otherwise,
there is no way at compile time to determine that obj should have access to
methods of other types. By declaring the variable "Object", the programmer is
deliberately telling the compiler only to make Object attributes available to
it. To infer otherwise is for the compiler to predict all runtime scenarios -
not feasible.

Casting the expression to a subtype informs the compiler that, if the cast is
permitted, the expression has access to the attributes and methods of that
subtype.

One should not denigrate this valuable and useful aspect of the Java language.
 
T

Twisted

One should not denigrate this valuable and useful aspect of the Java language.

I never did. I think you misunderstood my earlier post, or lost track
of the context, which was inside a branch condition using
"instanceof".

It wouldn't be too big a problem in static analysis for the compiler
to consider foo to be a Book inside

if (foo instanceof Book) {
...
}

:)

Of course, most usually you want polymorphism rather than instanceof
conditionals, but sometimes (such as trying to make something act
polymorphic also in an argument type, like math operations) you may
need to.
 
H

Hendrik Maryns

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Manivannan Palanichamy schreef:
(Book)obj.getISBN() -- this expression can be rewritten like,
Book tmp = (Book)obj;
tmp.getISBN();

No, it cannot! Actually, I am pretty sure it will give a
ClassCastException at runtime, unless the ISBN is also an instance of
Book, which seems unlikely. It will probably not even compile with a
symbol not found error, since obj will probably not have the method
getISBN() (I assume it is an Object).

What you are describing here is equivalent to

((Book)obj).getISBN();

Note how the obj is cast to book, and only then the getISBN() is called
/on the cast object/.

I suppose the OP made an error when copy/pasting(??) the example code.

Your signature is broke: there has to be a space after the two dashes!

Cheers, H.
- --
Hendrik Maryns
http://tcl.sfs.uni-tuebingen.de/~hendrik/
==================
http://aouw.org
Ask smart questions, get good answers:
http://www.catb.org/~esr/faqs/smart-questions.html
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFGlK83e+7xMGD3itQRAr38AJ9f0J1a5w2yt3UbH6GDDaorq5kuTQCfaK3T
sQr+ITsP8HUGzjE/dPT626c=
=dz4A
-----END PGP SIGNATURE-----
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,128
Latest member
ElwoodPhil
Top