Class.getDeclaredMethods()反射不需要的行为

时间:2017-04-25 10:01:11

标签: java reflection

我有一个A类,它是一个抽象类,B类是具体的,并扩展了A。

调用B.class.getDeclaredMethods()除了返回B类之外还返回A类的方法签名,但是JAVA文档在getDeclaredMethods()

上说了一些不同的东西
  

“这包括公共,受保护,默认(包)   访问和私有方法,但不包括继承的方法。“

所以从上面的文档中我期待从抽象父类继承的方法foo()不应该从getDeclaredMethods()调用返回,但我得到的方法foo()继承自抽象父类返回来自getDeclaredMethods()来电。

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() {
    }
}

有人可以解释一下这种行为。

enter image description here

4 个答案:

答案 0 :(得分:19)

你得到这个的原因是因为超类具有包级访问权限。 如果您将类A的访问修饰符更改为public(您需要将其放在自己的文件中),B.class.getDeclaredMethods()中的额外方法就会消失。

(另请注意,在课程abstract上修改的A是一个红色的鲱鱼:当课程A不是抽象的时候会发生同样的事情)

对于反射中的错误,这是Java编译器中的解决方法:虽然foo是一个公共方法,但它是在包作用域类A中定义的。您可以反思类B,找到方法,尝试使用反射调用它,只获得IllegalAccessException

编译器将在类B中生成桥接方法,以便您可以正确地反射调用方法foo

如果您在foo A方法中创建方法final,则最好地证明这一点,这使得无法修复此反射错误(无法覆盖该方法)< / p>

AB位于包abc中,而类C位于包def中。类C尝试反射性地调用公共类foo上的方法B,但它失败了,因为它是在非公共类A中定义的。

  

线程“main”中的异常java.lang.IllegalAccessException:Class   def.C无法使用修饰符“public”访问类abc.A的成员   最后的“

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());
    }
}

只需从方法final中删除foo关键字即可解决问题,因为编译器会在类B中插入合成桥方法。

在此错误报告中对此进行了解释:

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

  

描述

     

以下程序在运行时因此错误而失败:

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  }
  

评价

     

建议在这些非常罕见的情况下添加桥接方法来修复   反思中的问题,没有其他的修正或解决方法。   具体来说,我们将在公共方法时生成桥接方法   是从非公共类继承到公共类。

答案 1 :(得分:13)

由于其他答案列出的原因,有时编译器必须在类文件中添加一些棘手的代码;这可以是字段,构造函数或方法的形式。但是,它始终将这些字段标记为synthetic。这是它添加的实际修饰符,您可以使用以下方法检查方法是否是合成的:

method.isSynthetic()

因此,每当您获得所有方法时,使用此方法过滤列表以仅选择您在源中实际声明的那些;)

合成代码的其他示例包括:自动添加的默认构造函数,如果您有非静态内部类,则在字段中引用外部类。

答案 2 :(得分:1)

getDeclaredMethods()中的奇怪之处 - 它位于B的类文件中,其正文只调用super.foo()

我完全没有理解它,但它似乎与foo()包中声明的公共方法有关-private 超类。

一些测试用例:

  • A package-private,foo public(根据问题):方法在B生成
  • A package-private,foo package-private:方法未在B
  • 中生成
  • A public,foo public:方法未在B
  • 中生成
  • A public,foo package-private:方法未在B
  • 中生成

我怀疑这个想法是在另一个包装中的第三个班级不能看到&#34;&#34;&#34; A,但A.foo()仍然是public,因此可以通过B访问(?)。为了使可以访问它,B需要&#34;重新声明&#34;它

我不清楚这实际上是正确的 - 上面的(?)。 JLS 6.6.1陈述(强调我的):

  

引用类型的成员(类,接口,字段或方法)或类类型的构造函数只有在类型可访问时才可访问并声明成员或构造函数允许访问

但是这个代码 允许在另一个包中使用:

B b = new B();
b.foo();

答案 3 :(得分:0)

您似乎找到了尚未修复的a bug