I know that the bytecode specification allows classes to have methods with the same signature, differing only in the return type, unlike in the Java language. Some languages even make use of that under certain circumstances. My question is related to reflection:
if in a class I find a (non-private) method with the same name and parameter types as (a non final, non private) one its superclass , and with a return type equal or being a subtype of the return type of the said method in the superclass, when can I assume that code invoking the 'supermethod' statically will always result in the execution of the 'overriding(?)' method (naturally assuming the call is made on an object which is of that class)? Even in cases of other languages compiled to the JVM bytecode, or if runtime code generation is involved, or in hacked synthetic classes like the lambda forwarders?
My question was brought about from noticing how in the Scala standard library, an Iterable[E]
has a method:
def map[O](f :E => O) :Iterable[E]
while a Map[K, V]
, a subclass of Iterable[(K, V)]
declares another method:
def map[A, B](f :((K, V)) => (A, B)) :Map[A, B]
The actual signatures are more complex than here, but the principle is the same: after erasure, the method in Map
could override the method in Iterable
.
The fact of overriding is determined by the JVM by the exact equality of method descriptors (which include both parameter types and the return type). See JVMS §5.4.5, §5.4.6.
Therefore, on the JVM level, a method returning Map
does not override a method returning Iterable
. Compilers typically generate a bridge method returning Iterable
, implemented with a simple delegation to a method returning Map
.
Example:
class A<T extends Iterable> {
T map() {
return null;
}
}
class B extends A<Collection> {
@Override
Collection map() {
return null;
}
}
Here B.map
overrides A.map
, even though the return types differ. To make such hierarchy possible and valid, compiler generates (on the bytecode level) another method B.map
that returns Iterable
:
class B extends A<java.util.Collection> {
B();
0: aload_0
1: invokespecial #1 // Method A."<init>":()V
4: return
java.util.Collection map();
0: aconst_null
1: areturn
java.lang.Iterable map();
0: aload_0
1: invokevirtual #7 // Method map:()Ljava/util/Collection;
4: areturn
When a virtual method A.map
is invoked on an instance of B
, a method B.map:Iterable
is always called, which in turns calls B.map:Collection
.