Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class.getDeclaredMethods() of reflection peculiar behavior

I have a abstract class A, class B is concrete class that extends A.

Calling B.class.getDeclaredMethods() returns class A's method signatures in addition to class B's but JAVA documentation says some thing different on getDeclaredMethods()

"This includes public, protected, default (package) access, and private methods, but excludes inherited methods."

So from above docs i was expecting method foo() which is inherited from abstract parent class should not be returned from getDeclaredMethods() call, but i am getting method foo() which is inherited from abstract parent class is returned from getDeclaredMethods() call.

import java.lang.reflect.*;

public class B extends A {
    public static void main(String[] args) throws Exception {
        Method[] methods = B.class.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            System.out.println(methods[i]);
        }
    }
}


abstract class A {
    public void foo() {
    }
}

Can some one explain me this behavior.

enter image description here

like image 864
Sachin Sachdeva Avatar asked Apr 25 '17 10:04

Sachin Sachdeva


2 Answers

The reason you get this is because the superclass has package level access. If you change the access modifier of class A to public (you'll need to put it in its own file), the extra method in B.class.getDeclaredMethods() disappears.

(Also note that the abstract modified on class A is a red herring: the same thing occurs when class A is not abstract)

This is a workaround in the Java compiler for a bug in reflection: although foo is a public method, it was defined in package scoped class A. You could reflect on class B, find the method, try to invoke it using reflection, only to get an IllegalAccessException.

The compiler will generate a bridge method in class B so that you can correctly reflectively invoke method foo.


This is best demonstrated if you make the method foo in A a final method, which makes it impossible to fix this reflection bug (it's not possible to override the method)

Classes A and B are in package abc and class C is in package def. Class C tries to reflectively invoke method foo on class B which is public, but it fails because it was defined in non-public class A.

Exception in thread "main" java.lang.IllegalAccessException: Class def.C can not access a member of class abc.A with modifiers "public final"

package abc;

public class B extends A {
}

class A {
    public final void foo() {
    }

}
package def;

import java.lang.reflect.Method;

import abc.B;

public class C {
    public static void main(String[] args) throws Exception {
        Method m = B.class.getMethod("foo");
        m.invoke(new B());
    }
}

Just removing the final keyword from method foo resolves the problem, because the compiler then inserts the synthetic bridge method in class B.


It's explained in this bug report:

http://bugs.java.com/view_bug.do?bug_id=6342411

Description

The program below fails at runtime with this error:

Exception in thread "main" java.lang.IllegalAccessException: Class refl.ClientTest can not access a member of class refl.a.Base with
modifiers "public"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
        at java.lang.reflect.Method.invoke(Method.java:578)
        at refl.ClientTest.main(ClientTest.java:9)
========== test/refl/a/Base.java ========== 
     1  package refl.a; 
     2   
     3  class Base { 
     4      public void f() { 
     5          System.out.println("Hello, world!"); 
     6      } 
     7  } 
========== test/refl/a/Pub.java ========== 
     1  package refl.a; 
     2   
     3  public class Pub extends Base {} 
========== test/refl/ClientTest.java ========== 
     1  package refl; 
     2  import refl.a.*; 
     3  import java.lang.reflect.*; 
     4   
     5  public class ClientTest { 
     6      public static void main(String[] args) throws Exception { 
     7          Pub p = new Pub(); 
     8          Method m = Pub.class.getMethod("f"); 
     9          m.invoke(p); 
    10      } 
    11  }

EVALUATION

The proposal is to add bridge methods in these very rare cases to fix a problem in reflection with no other forseen fix or workaround. Specifically, we would generate a bridge method when a public method is inherited from a nonpublic class into a public class.

like image 159
Erwin Bolwidt Avatar answered Nov 02 '22 15:11

Erwin Bolwidt


For the reasons listed by the other answers, sometimes the compiler have to add some tricky code to your class file; this can be in the form of fields, constructors or methods. However, it always mark those fields as synthetic. That's an actual modifier it adds, and you can check if the method is synthetic with the method:

method.isSynthetic()

So whenever you get all methods, filter your list with this method to select only the ones you actually declared in the source ;)

Other examples of synthetic code are: default constructors that get automatically added, a reference to the outer class in a field if you have a non-static inner-class.

like image 13
Luan Nico Avatar answered Nov 02 '22 14:11

Luan Nico