NOTE: This discussion and all examples within assume the use of Java 5.0
The Code... what appears to be a override of speak() in Second.
Second is not being overridden, its being overloaded ( or some strange thing like overloading ). But wait you say, the rule for overloads is that the argument list ( the signiture of the method ) must be different, so the compiler knows which one to call. That is what I thought too...
Covariant return types in Java 5. The rule states that "a method in a subclass may return an object whose type is a subclass of the type returned by the method with the same signature in the superclass."
Now lets break down this example as I see it. I see a super class named First, and a sub class named Second. The subclass overrides the speak() method, and because of Java 5's covariant return type rule, changes to return type to String. String is a subclass of Object, so this is legal in Java 5. Now we have a main method, create a reference of type First named obj that points to a instance of Second. Ah now some standard subtype polymorphism about to happen ... the compiler will use dynamic method lookup to see that obj points to a Second and will call the overridden version of speak in Second. OHHH WTF ITS TRYING TO USE THE VERSION IN FIRST??? How could this be I thought to myself.
I went and re-read all about overriding and overloading methods, some polymorphism stuff, rules about covaraint return types.. bla bla bla to make sure I didnt forget some rule. Why oh why was it calling the superclass version, when it should be calling the version in the class that the obj reference points to...
So I then use reflection to find out just what methods the subclass has. I wrote a function that takes in a reference to an object, and then prints out all information about that objects methods. So using it like this
Name: method1 Return Type: java.lang.Object Parameter Types: Name: method1 Return Type: java.lang.String Parameter Types: Name: hashCode.....then all the other methods in Object like toString etc.
It overloads the method, instead of overriding it. Is that what is going on here? This breaks all the rules of overloading if so.. What is the point of this feature?
[Kenny]: Second is not being overridden, its being overloaded
No, nothing in this problem is being overloaded. The method speak() in First is being overridden bey the method speak() in Second. That's it.
[Kenny]: Ah now some standard subtype polymorphism about to happen ... the compiler will use dynamic method lookup to see that obj points to a Second and will call the overridden version of speak in Second. OHHH WTF ITS TRYING TO USE THE VERSION IN FIRST??? How could this be I thought to myself.
The compiler does not use dynamic method lookup, ever. "Dynamic" means the lookup occurs at run time, after the compiler has, errr, compiled. At compile time, the compiler simply assumes that reference obj holds and instance of the declared type of the reference, which is First. You've told the compiler it's a First, and the compiler will continue to assume it's a First. Sure, you've put a Second instance in there, and that's pretty obvious in this case. But the compiler is still just going to assume it's a First, because that's what it's required to do. And because compile-time determination of what type a reference "really" has is often much more complex than it is here, or even impossible. It's much simpler for everyone if the compiler gives consistent behavior that is easy to predict - and the best way to do that, in the opinion of Java's designers, is for the compiler to just assume that a reference holds it's declared type, period. So obj holds a First, and speak() in First returns a String, period - as far as the compiler is concerned. If you want the compiler to treat obj as a Second, you need to declare it as a Second.
I think the thing you may be missing is that while, as Jim says, the compiler can only use the method signatures that appear in First when compiling method calls on a reference variable of type First, if you change that last line to use an Object variable, compile and run the code, you'll see that the overrridden method in class Second is the one that is actually invoked. Try it.
Oops Im' sorry, I ment to say the JVM will perform dynamic method lookup.Also, if there is no overloading taking place, why does reflection say Second contains both methods? [ January 13, 2007: Message edited by: Kenny Johnson ]
Joined: Jan 10, 2007
Yes. if you replace the last line with (just inserting the cast to String), the compiler is happy and the JVM will call the speak()-method of class Second.
Joined: Jan 30, 2000
[Jim]: So obj holds a First, and speak() in First returns a String,
I misspoke here; the return type is Object.
[Kenny]: Also, if there is no overloading taking place, why does reflection say Second contains both methods?
This is a side effect of how they've implemented covariant returns, redeclaring the superclass method in the subclass with the old return type, and having that methods forward to the the new method. The redeclared method is called a bridge - it's an implementation detail seen by the JVM, not normally accessible from Java source code (other than through reflection). Note that if you try to invoke the First method using an instance of Second, you will still get the subclass method, not the superclass. That's overriding.
[ January 13, 2007: Message edited by: Jim Yingst ]