The relation between the declared type and created type
-
01-06-2021 - |
Question
I have an question about the following code (Is this call dynamic binding?). I feel confused about 3 point.
First, what is the mean of the variable pq? Does pd still be the data type of P or be the Q?
Second, when I invoke the pq.m(pp) method, why the result become Q::P but not P::Q?
Finally, what is this mean ((P) qq).m(qq);? I hope somebody could solve my problem.
The result of the following code will be
P::Q, Q::P, Q::Q, R::P, Q::P, Q::Q, Q::Q
class Test {
public static void main(String[] args) {
P pp = new P();
Q qq = new Q();
R rr = new R();
P pq = qq;
pp.m(qq);
pq.m(pp);
pq.m(qq);
rr.m(pp);
qq.m(pq);
qq.m(qq);
((P) qq).m(qq);
}
}
class P {
public void m(P p){System.out.println("P::P"); }
public void m(Q p){System.out.println("P::Q"); }
public void m(R c){System.out.println("P::R"); }
}
class Q extends P {
public void m(P p){System.out.println("Q::P"); }
public void m(Q p){System.out.println("Q::Q"); }
public void m(R c){System.out.println("Q::R"); }
}
class R extends Q {
public void m(P p){System.out.println("R::P"); }
public void m(Q p){System.out.println("R::Q"); }
public void m(R c){System.out.println("R::R"); }
}
Solution
P pq = qq;
means that pq
is known to the rest of the program as a type P
. But as the creator, you know it's really of type Q
. So this means that when you call pq.m()
, it's really calling the implementation from class Q.
It's called overriding a method. So when you call pq.m(pp), you are really calling:
public void m(P p){System.out.println("Q::P");
because that is the method from class Q.
If Q did not have a m(P) method, then it would automatically call the superclass method, the one from P.
((P) qq).m(qq);
is the same as doing:
P pqq = (P)qq; // pqq is known as P type, but it's instance is still the original Q type
pqq.m(qq); // Again, since pqq is truly an instance of Q, it calls Q.m(Q)
You should really read about inheritance. This is a bigger subject than can be explained here.
All this being said, your example doesn't illustrate its power well. But for example, if class Q had an extra method, public void sayHello();
, then
Q q = new Q();
P p = new Q();
q.sayHello(); // This would be legal
p.sayHello(); // This would be illegal because the compiler knows p as a declared instance of P, even though you know it's truly a Q.
((Q)p).sayHello(); // This would be legal because you told the compiler to look at p as an instance of Q. It's called a cast.
I hope this all helps. Be sure to go read up on object orientation.
OTHER TIPS
Starting, I think you mean pq
, not pd
. Since Q extends P, Q is also a P type. It's like saying that an apple is a fruit. So you take the apple (Q) and says: It's a fruit (P). When you call the pq
methods, they will call the methods from the Q class, since pq
is still a Q object.
In the last part, when you do ((P) qq).m(qq);
, is the same as doing the following:
P p = (P) qq;
q.m(qq);
So as said above, the code will still call the method from the Q class, printing "Q::Q"
Dynamic binding, and therefore polymorphism, only works for the object to the left of the dot in a method call (o.m(x)
-- only for o
). The arguments types are resolved statically, at compile time. Take a more well-known situation:
class A {
public boolean equals(A other) {
System.out.println("A.equals called"); return true;
}
}
A a1 = new A(), a2 = new A();
Object o = a1;
o.equals(a1); // doesn't print anything
a1.equals(o); // doesn't print anything
a1.equals(a2); // prints "A.equals called"
The point here is that class A doesn't override Object.equals(Object), but instead just adds another overloaded method A.equals(A) -- and that one gets called only when the argument is of a declared type A.