IY iy = new D();
C c1 = (C) iy; // What is going on here...
c1.doOther(); // and here?
public interface IX {
void doIt();
}
public interface IY {
void doOther();
}
public class A {
public void doIt(double d) {
System.out.println("Doit A " + d);
}
}
public class B extends A implements IX {
public void doIt() {
System.out.println("Doit B");
}
public void doIt(int i) {
System.out.println("Doit B " + i);
}
}
public class C extends A implements IY {
public void doIt() {
System.out.println("Doit C");
}
public void doOther() {
System.out.println("DoOther " + this.getClass().getSimpleName());
}
}
public class D extends C {
public void doIt() {
System.out.println("Doit D");
}
}
Prints out DoOther D
Why does this happen? I am learning about Javas type system and I am super confused.
What exactly is happening on each step? I will try and give my understanding of what is happening.
IY iy = new D()
compiles and runs fine due to the fact that D
inherits from C
which inherits from IY
. Due to the transitivity rule (D <: C <: IY => D <: IY)
D
is sub to IY
and it works to store a D-object
into a variable of type IY
because D
is a type of IY
.
C c1 = (C) iy
, here is where I get confused. To my understanding we are casting a D-object
into a C-object
. Or is it something else that happens? Am I maybe casting a variable of type IY
(interface) to the type C
? The type changes, but the value of the variable (which is a reference to a D-object
) remains the same between iy
and c1
?
This works because C
is super to D
. Now this is a dangerous thing to do if I have understood this correctly, due to the fact that this will not always run because it is not guaranteed that C
and D
have a relationship, therefore it can fail during runtime and give the ClassCastException
exception, is this correct?
But then the result of c1.doOther()
confuses me. If c1
is a C-object
and we do c1.doOther()
, isn't the C-object c1
calling its own instance method doOther()
? If that is the case then the line this.getClass().getSimpleName()
should return DoOther C
but it returns DoOther D
instead as if it was the D-object
calling its super method, which seems to be the actual case?
What is really going on here?
casting a D-object into a C-object
This is not how I would describe it. The cast (C)
checks the type of the object. If it passes, then the assignment is made. If not then an error is thrown.
No changes or other from-one-"into"-another is done. The cast only tests that the object is of the requested type. Once that object is found to be of the requested type, then operations (methods) relating to that type are allowed.
A corollary operation is the instanceOf
keyword, which allows you the programmer to test a type without throwing an error.
if( iy instanceOf C ) {
// do C stuff
}
else
System.err.println( "I couldn't do it." );
If c1 is a C-object and we do c1.doOther(), isn't the C-object c1 calling its own instance method doOther()? If that is the case then the line this.getClass().getSimpleName() should return DoOther C
The type system is basically in two halves. There's the type you declare to the compiler (here IY
and then you cast to C
), and then there's the actual real type of the object. Operations that are allowed (ie method calls) are based on the declared type.
However, the actual call is based on the type of the object itself, which in this case is always going to be a D
regardless of the declared type. So you always get D
's interpretation of being an IY
or a C
type. It's always D
that is being called here. Again, there is no changing based on casting or assignment. The type of D
is "polymorphic" and can "look like" an IY
or C
, but it's always really a D
.
TL;DR: Casting doesn't change the underlying instance's type, it just exposes it as a variable of a different type to all later code.
Analogy:
You have a box, and you put an accountant named Alice in it. You put a label on that box that says 'Accountant'.
You then put another label on it that says 'senior accountant'. This hasn't changed Alice in the box. Alice doesn't do accounting any different to any other senior accountant, so they don't override that behaviour.
When you ask Alice to doAccounting
, since they haven't overridden that behaviour, the basic senior accountant workflow for doAccounting
is run, but as part of that workflow there is a step that gets the accountants name to sign the document.
Would you be surprised that the resulting signature on the document said 'Alice'.
More detail:
IY iy = new D(); // (1)
C c1 = (C) iy; // (2)
c1.doOther(); // (3)
(1) You create an instance of type D
, and store it as a variable iy
. You give this variable the type IY
, which means that anything using that variable will only see that is an instance of IY
. This does not mean that it isn't a D
anymore, it's just you're not exposing that to any later code. But the variable is just a 'pointer' to the newly created D
instance.
(2) You cast the D
instance you created in step (1) to type C
, and store this in a new variable c1
. This is evaluated at runtime, and it succeeds as the iy
variable being cast is of type D
(as discussed above) and by your class definitions D extends C
- ie all D
s are C
s (but not vice-versa). You declare the c1
variable as having type C
, which is fine - but that does not change the fact that the underlying instance is still the D
that you created above. Again, c1
is now a 'pointer' to the previous D
instance. You've just exposed to later code that is at least of type C
.
(3) You call an instance method doOther
on your c1
variable. This is invoked on the underlying instance that c1
points to. As discussed above, this is still the same instance of type D
that you created in step (1). So it looks at that instances doOther
method. This then looks if type D
has an override of doOther
to call, but since it doesn't goes to the next one in the hierarchy which is from type C
. As part of the method, getClass()
is used. Much like the doOther
call, this is invoked on your D
instance, and that does have its own getClass()
method (because all new classes will). So the getClass()
from type D
is called, and you see the result you see.
IY iy = new D();
C c1 = (C) iy;
c1.doOther();
As per the code you shared IY, C, A are parent of D. Whatever type you changing your D object, it is still D object only the reference are going to change, not the D object. When you call c1.doOther() it is calling with the D object, you are getting DoOther D.
The type only restrict what ever method you can call. If the reference is IY then you can only able to call doOther. If it point to C, then you can access everything C have. It will go like this...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.