Class.getDeclaredMethods() is returning inherited methods
Peter Levart
peter.levart at gmail.com
Wed Jan 2 17:45:12 UTC 2019
Hi Steve,
What you have observed is an artifact of how Java the language is
compiled into bytecode contained in .class files and the fact that Java
reflection API returns information about the compiled .class files. The
quoted sample code:
package x.y;
class A {
public void m() {
}
}
public class B {
}
...is one example where the compiler must create a synthetic method in
class B which overrides public method in class A and delegates to it.
Java access checks allow invoking A.m() only from package x.y since A is
a package private class. OTOH m() is a public method and as such is
inherited by B. so m() can be called on an instance of type B from
anywhere because B is a public class. To accommodate that, javac
synthesizes public method B.m() which delegates to A.m() as thought you
would have written the following code:
package x.y;
class A {
public void m() {
}
}
public class B {
public void m() {
super.m();
}
}
... B.class.getDeclaredMethods() therefore returns this synthetic method
B.m() and not the inherited method A.m(). You can verify that by
invoking .getDeclaringClass() on it, which should give you B.class and
not A.class.
Strictly speaking such synthetic method on B is not really needed by JVM
to invoke A.m() from anywhere via the instance of B. Invocation of m()
given an instance of B could be compiled by javac in exactly the same
way even if there was no synthetic method B.m(). Javac could pretend
there is a method B.m() and emit invoke instruction referencing the
imaginative method. At runtime JVM would dispatch the virtual call to
inherited A.m and allow such call from anywhere since it references a
public method in a public class (although imaginative).
The only reason javac generates synthetic method B.m() is to actually
allow A.m() to be called via reflection API. Reflection API does not
allow invoking A.m() directly from anywhere, but it allows invoking the
synthetic B.m() which then delegates to A.m().
If we wanted to get rid of this synthetic method, reflection would have
to be fixed 1st to accommodate calling public methods inherited from
non-public classes if they are called via an instance of a public
subclass (or a subclass of it). The reason this has not been done yet is
probably that it is more tricky that it seems at first. Imagine the
following situation:
package p1;
class A {
public void m() {
}
}
public class B extends A {
}
package p2;
class C extends B {
}
public class Builder {
public static B build() {
return new C();
}
}
package p3;
public class App {
public static void main(String[] args) {
B b = p2.Builder.build();
b.m(); // << HERE
}
}
...javac would have an easy job here. It knows the static (compile-time)
type of variable b (which is a public class B) so it can directly emit
invoke instruction for imaginative method B.m() here.
Reflection API OTOH has a more difficult job in the following code:
package p3;
public class App {
public static void main(String[] args) {
B b = p2.Builder.build();
Method m = B.class.getMethod("m"); // m.getDeclatingClass() ==
A.class
m.invoke(b); // << HERE
}
}
...reflection has at its disposal:
- a Method object 'm' representing method A.m() which is located in a
non-public class p1.A
- a target instance 'b' which is of type p2.C which is also a non-public
class
- the invocation is being performed from package p3
In order for reflection to allow such invocation it would have to assume
that such invocation is performed via some imaginative method X.m() such
that:
- X is a public class and is a subclass of A
- C is a subclass of X
Such decision is inherently non-local meaning that reflection would have
to search class hierarchy to allow it. It is possible though, but
probably to complicated for such use case.
What do others think?
Regards, Peter
On 1/2/19 5:33 PM, Steve Groeger wrote:
> I am looking into an issue where the Class.getDeclaredMethods() is
> returning inherited methods,
> where the Java Doc here:-
> https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Class.html#getDeclaredMethods()
> states:-
>
> "Returns an array containing Method objects reflecting all the declared
> methods of the class or interface represented by this Class object,
> including public, protected, default (package) access, and private
> methods, but excluding inherited methods".
>
> with the last part of the statement being the relevant part, "but
> excluding inherited methods"
>
> This was raised as an issue a long time ago:
> https://bugs.openjdk.java.net/browse/JDK-6815786 but has not been fixed
> and is still an issue in JDK11.
>
> Before I go looking into why this is occurring and producing a fix, is
> this still seen as an issue and does it need to be fixed / should it be
> fixed.
> I dont want to do lots of investigation and produce a fix just to be told
> we cant contribute this as it will break too many people that might have
> been using this feature !!!!
>
> Thanks
> Steve Groeger
> IBM Runtime Technologies
> Hursley, Winchester
> Tel: (44) 1962 816911 Mobex: 279990 Mobile: 07718 517 129
> Fax (44) 1962 816800
> Lotus Notes: Steve Groeger/UK/IBM
> Internet: groeges at uk.ibm.com
>
> Unless stated otherwise above:
> IBM United Kingdom Limited - Registered in England and Wales with number
> 741598.
> Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU
> Unless stated otherwise above:
> IBM United Kingdom Limited - Registered in England and Wales with number
> 741598.
> Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU
More information about the core-libs-dev
mailing list