I was trying to write some polymorphic application code and found that
a superclass method implementation gets invoked when I expect a
subclass implementation. Here's how I have abstracted the problem.
I have a base class - ClassA. I have a subclass of ClassA - ClassB.
Both classes implement foo() - exact same method signature.
My application code instantiates instances of ClassB (and siblings)
via a marshaler, and only knows that the object it references is a
"kind of" ClassA. I simulate this as shown below.
ClassA a = (ClassA) Class.forName("ClassB").newInstance();
Now, if I send a.foo(), I see that the implementation overriden in
ClassB is invoked - as I had hoped and expected.
But really what I am aiming for is something with a different twist.
I have another class hierarchy with a base class - ClassX - and a
subclass - ClassY.
I have implemented ClassA.foo(ClassX) and ClassB.foo(ClassY).
Instances of ClassX and ClassY are created outside the context of the
code that invokes foo(...). All this code knows is that it's got a
reference to an object which is a "kind of" ClassX. I simulate this as
shown below.
ClassX x = (ClassX) Class.forName("ClassY").newInstance();
If, in this scenario, I send a.foo(x) I see that it is the super
implementation of foo(...) that is invoked rather than the subclass
implementation of foo(...).
This was a big surprise to me. And it may seem as puzzling to some
that I found it surprising. After all, you note, the Java compiler
chose exactly the right implementation based on the information that I
provided.
I can kind of accept this, although I would argue that this behavior
makes polymorphic code really fragile and difficult to debug, since
runtime behavior is different from the language semantics due to ...
?compiler optimization?. (Coming to Java from a Smalltalk background,
as I do, the complexity here seems a bit bizarre)
But wait. By this reasoning, shouldn't I have gotten the superclass
implementation of foo(void) in the first scenario?
Just by way of exploring this puzzle a little further, I added another
scenario. Suppose I have a variable declared as "ClassB b", and send
"foo(x)" (where, as above 'x' is an instance of ClassY). This is at
least consistent with scenario #2 - I get the super implementation
because the compiler doesn't know that 'x' is actually a reference to
an instance of ClassY.
So far, then, it seems like scenario #1 is a bug, since - although
it's what I wanted - it's inconsistent with the other two scenarios.
Is there another explanation?
-rht
public class Testcase5 {
public static void main(String argv[]) {
ClassA a;
ClassB b;
ClassX x;
try {
a = (ClassA) Class.forName("ClassB").newInstance();
b = (ClassB) Class.forName("ClassB").newInstance();
x = (ClassX) Class.forName("ClassY").newInstance();
} catch (Exception e) {
System.err.println(e.getMessage());
return;
}
b.foo(); // out: "B.foo()"
a.foo(); // out: "B.foo()"
b.foo(x); // out: "A.foo(x)"
a.foo(x); // out: "A.foo(x)"
}
}