Java Class.getMethods() behavior on overridden met

2019-04-28 08:01发布

问题:

While writing a simple JSON serialiser using reflection in Java I was caught off guard by the behavior of Class.getMethods(). It appears that Java Class.getMethods() returns both overriding and overridden methods if the return type of the overriding method extends that of the overridden method.
So for example given the interfaces:

static interface A {
  A x();
  A y();
}
static interface B extends A {
  B x();
  A y();
}

A.class.getMethods() returns and array of two methods as expected however B.class.getMethods() returns an array of 3 methods (which to me was a little counter intuitive). Of the 3, 1 corresponds to the y() as expected but the remaining two correspond to the original x() with return type A and the overriding version of x() with return type B respectively. This struck me as a little odd to have but the original x() in the array since its not accessible from anywhere. Anyway my question is this:
Is there and easy way to get a list of only the most specialised versions of a class's methods without resorting to manually checking for overridden methods and filtering them out?

回答1:

My understanding is that if you filter out methods for which isBridge() returns true, the unwanted method should go away.

It is an artefact of how Java implements covariant return types (bridge methods are also used for generics, but that doesn't appear relevant to your use case).

edit Interestingly enough, while this works for classes, it doesn't seem to work for interfaces. All three methods of B are marked as non-bridge and non-synthetic. However, if I create a non-abstract class C implementing B, its A x() is marked as both bridge and synthetic.



回答2:

What you have is called "covariant return".

As aix points out, it seems you have to deal with bridge methods. Read this: http://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html (ignore the text about generics) and this: Problem in the GetDeclaredMethods (java)



回答3:

One way is to say: B.class.getDeclaredMethods(), however, this will return only the methods declated in B. So, if you have C extends B implements A, C.class.getDeclaredMethods() wont return the methods that you haven't overridden.

if you want to iterate, good way to do that is

for (Method m: B.class.getMethods()) {
  if (m.getDeclaringClass() == Object.class || m.declaringClass() == A.class) 
    continue;
  // otherwise do some stuff
}