views:

690

answers:

8

I have a class C. Class E extends it.

E e = new E();
C c = new C();

Why is

e = (E) c;

Upon further review: though numeric conversions have the same syntax as casting objects, some confusion arose. At any event, the above does not give a compilation, but rather a runtime error - so a class can be casted to subclass in some instances (otherwise the code would not compile). Any examples that anyone can give where the above works?

And also:

K extends M

K k = new K();

((M) k).getClass() gives K. Why is that? It was casted to the more general M!

Suppose I have a doIt() method implemented in both M and K. executing

((M) k).doIt();

gives M's or K's doIt()?

Thanks!

+6  A: 

Just because E extends C, C doesn't become an E... E on the other hand is a C

Edit: To expand Mark's comment below... Just because every woman is a human, not all humans are women. All humans share the "human interface" with legs, hands, faces, etc. Women extends it with functionality that returns good feelings when you provide diamonds and gold.

The int => double conversion is not even related as it is not a class cast but a conversion telling the compiler to store whatever is in x in y (which happens to be a double).

((M) k).getClass() gives K.

because k is still a K even if you cast it to an M or an Object (or something else it happens to be).

Edit: I think the confusion here is because you consider k to "become" an M when you cast it, it doesn't. You are just treating it as an M. If you ask someone who is a "dog owner" what kind of breed it is he will not return "It's a dog", the reason is simply that the getBreedName() method is likely to have been overridden in the subclass LabradorOwner to return "Labrador". It is the same with getClass(), it will return the class of the implementation. It will not be an M but a K that happens to be an M as well just because K extends M.

Fredrik
Why not? Every E is a C. So why can't I treat a C object as an E?For the second part - so what does casting do? It doesn't change the variable. What happens if I execute ((M) k).doIt() - will K's or M's doIt() be executed?
ooboo
You might as well ask: Every woman is a person; why can't I treat every person as a woman? Answer: you **can** - but first you need to *check* that they are a woman.
Marc Gravell
Every E is a C but not every C is an E. E extends C is like saying E adds to C therefore a C might not have the same amount of things in it that an E does.
Russ Hayward
Thanks Marc and Russ for filling in the blanks as comments. I am not sure if it means anything to mark comments as great but I've up them once each.
Fredrik
@Fredrik: You should feel free to take advantage of SO's wiki-like atributes, and edit your answer to clarify it. :)
Stu Thompson
Sure, I just felt the comments said it all and anyone seeing the response is will see the comments.
Fredrik
+2  A: 

the int/double is unrelated; that is a conversion, not a cast - there is no relationship between int and double.

Re the question; a type's object is fixed at creation. An object that is a C is not (and can never be) an E. However, you can treat an E as a C, since inheritance represents "is a". For example:

E e = new E();
C c = e;

Here we still only have one object - simply that the c variable thinks of it as a C, so won't expose methods specific to E (even though the object is an E).

If we then add:

E secondE = (E) c;

This is a type check; again, we haven't changed the object, but to put c into an E variable requires us to prove to the compiler/runtime that it really is an E. We didn't need this in the first example as it can already prove that any E is also a C.

Likewise, with the getClass() - all the cast does is change how the compiler thinks of the object; you haven't changes the object itself. It is still a K.

You need to separate variables from objects. The cast is talking about the variables; they don't change the object.

Marc Gravell
Thanks. Can you give an example where E secondE = (E) c; would work?
ooboo
He just did... when you do: E e = new E(); C c=e and then make an E out of it again with "E secondE = (E) c"; Because the object has been an E all the time (even if someone treated it as the C it is by inheritance) the third row will be perfectly legal.
Fredrik
In real code btw, the third line would probably be inside some "if (c instanceof E) check... If you'd known all the time that your c is an E it wouldn't make sense to cast it to a C in line two.
Fredrik
A: 

For the first question, you cannot cast a superclass to a subclass because a subclass adds members that the superclass doesn't have. How is the compiler supposed to know what values to put there when it's casting? Basically, an E is a C, but a C is not an E.

getClass() gets the type of the object in memory. Casting to M simply hides the fact that it's a K, it doesn't change the underlying object.

Sean Nyman
+3  A: 

You can't cast objects in Java.

You can cast references in Java.

Casting a reference doesn't change anything about the object it refers to. It only produces a reference of a different type pointing to the same object as the initial reference.

Casting primitive values is different from casting references. In this case the values do change.

Joachim Sauer
+1  A: 

To add to Frederik's answer, casting an object to something doesn't change it's type. Also, object's can only be cast to a type it already is (the compiler just doesn't know at that point) That's why impossible casts will never be accepted:

Integer i = (Integer) new String();

will not compile, because the compiler knows it can't be possible.

Jorn
+3  A: 

Consider a real-world example:

public class Dog extends Animal

All dogs are animals, but not all animals are dogs. Hence...

public class Cat extends Animal

Casting an Animal to a Dog can only be done if the Animal in question is indeed a Dog. Otherwise it would force the Universe to infer properties unique to a dog (wagging tail, barking, etc.) onto an Animal. That Animal might well be a Cat with properties unique to it (purring, rigorous regime of self-cleaning, etc.). If the cast is not possible then a ClassCastException is thrown at runtime.

Nobody wants a dog that purrs.


((M) k).getClass() gives K. Why is that? It was casted to the more general M!

You've casted k to M, but all classes have a getClass() method. k's class is always K, regardless of whather you cast its reference to M or not. If you cast a Dog to an Animal and ask it what animal it is it'll still answer that it's a dog.

In fact, casting to a superclass is redundant. A Dog already is an Animal and it has all the methods of an Animal as well as its own. Many Code Analysis tools such as FindBugs will notify you of redundant casts so you can remove them.


Suppose I have a doIt() method implemented in both M and K. executing

((M) k).doIt();

gives M's or K's doIt()?

K's doIt() for the same reasons as above. The cast operates on the reference; it doesn't transform an object to a different type.


Can you give an example of when casting (Dog doggy = (Dog) myAnimal) makes sense?

Sure can. Imagine a method that receives a list of animals for processing. All the dogs need to be taken for a walk, and all the cats need to be played with using a bird-shaped toy. To do this we call the takeForWalk() method that only exists on Dog, or the play() method which only exists on Cat.

public void amuseAnimals( List<Animal> animals ) {
    for ( Animal animal : animals ) {
         if ( animal instanceof Dog ) {
             Dog doggy = (Dog)animal;
             doggy.takeForWalk( new WalkingRoute() );
         } else if ( animal instanceof Cat ) {
             Cat puss = (Cat)animal;
             puss.play( new BirdShapedToy() );
         }
     }
}
banjollity
Why does it execute K's doIt? I tell it to refer to this object as an M, not a K. If the compiler treats it as an M, it should execute M's methods.I'm even more confused now. Can you give an example of when casting (Dog doggy = (Dog) myAnimal) makes sense?
ooboo
"If the compiler treats it as an M, it should execute M's methods.". The compiler treats it as an M for reasons of typing. If K cannot be typed as an M then a ClassCastException gets thrown. K's methods are used because as a subclass K can override M's methods. This is what separates a K from an M and is an intrinsic part of Object Orientation.
banjollity
A: 

((M) k).getClass() gives K. Why is that? It was casted to the more general M!

A useful analogy (that I got from Bill Venners' site artima.com) that may help clear the confusion is that the difference between classes and objects is like the difference between an architect's blueprint and the actual house that is built. The blueprint exists on paper and is a concept, while the house exists in real life. You can have more than one house built to the same blueprint.

How is that relevant to this question? Let's say there's a McMansion blueprint and a McMansionWithHeatedPool blueprint. A McMansionWithHeatedPool is an extension of a McMansion with a heated pool.

Now, if you see a real McMansionWithHeatedPool, for that object:

  1. Conceptually (i.e., if you look at the architect's blueprints), you would see that a McMansionWithHeatedPool is clearly also a McMansion. Hence the upcast is allowed. (For the same reason, a McMansion object cannot be type cast into a McMansionWithHeatedPool : no heated pool!)

  2. ((McMansion) k).getClass() gives McMansionWithHeatedPool because k is still a McMansionWithHeatedPool. The type cast is on the expression, not on the object.

vijucat
+1  A: 

"If the compiler treats it as an M, it should execute M's methods."
The compiler treats the reference as M. The instance that the reference points to is of type K, not M. You can't cast the reference and assume that means the instance will suddenly change behavior. What the compiler does is make sure that the method you invoke on the specified reference exists. It does not have anything to do with which implementation is invoked, only that an implementation does exist.