简体   繁体   English

Class.getDeclaredMethods() 反射特有的行为

[英]Class.getDeclaredMethods() of reflection peculiar behavior

I have a abstract class A, class B is concrete class that extends A.我有一个抽象类 A,类 B 是扩展 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()除了类 B 之外,调用 B.class.getDeclaredMethods() 还返回类 A 的方法签名,但 JAVA 文档在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.所以从上面的文档我期待从抽象父类继承的方法 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() {
    }
}

Can some one explain me this behavior.有人可以向我解释这种行为吗?

在此处输入图片说明

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.如果您将类A的访问修饰符更改为public (您需要将其放入其自己的文件中),则B.class.getDeclaredMethods()的额外方法将消失。

(Also note that the abstract modified on class A is a red herring: the same thing occurs when class A is not abstract) (另请注意,在A类上修改的abstract是红鲱鱼:当A类不是抽象时,也会发生同样的事情)

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 .这是Java 编译器中针对反射错误的解决方法:尽管foo是一个公共方法,但它是在包作用域类A定义A You could reflect on class B , find the method, try to invoke it using reflection, only to get an IllegalAccessException .您可以反思B类,找到该方法,尝​​试使用反射来调用它,结果却得到了IllegalAccessException

The compiler will generate a bridge method in class B so that you can correctly reflectively invoke method foo .编译器将在类B生成桥接方法,以便您可以正确地反射调用方法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)如果您将A的方法foo设为final方法,则最好证明这一点,这使得无法修复此反射错误(无法覆盖该方法)

Classes A and B are in package abc and class C is in package def . A类和B类在abc包中, C类在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 .C尝试反射性地调用公共类B上的方法foo ,但它失败了,因为它是在非公共类A定义A

Exception in thread "main" java.lang.IllegalAccessException: Class def.C can not access a member of class abc.A with modifiers "public final"线程“main”中的异常java.lang.IllegalAccessException:类def.C无法访问带有修饰符“public final”的类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());
    }
}

Just removing the final keyword from method foo resolves the problem, because the compiler then inserts the synthetic bridge method in class B .只需从方法foo删除final关键字即可解决问题,因为编译器随后在类B插入了合成桥接方法。


It's explained in this bug report:在此错误报告中对此进行了解释:

http://bugs.java.com/view_bug.do?bug_id=6342411 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.具体来说,当公共方法从非公共类继承到公共类时,我们将生成桥接方法。

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 .但是,它始终将这些字段标记为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.合成代码的其他示例包括:自动添加的默认构造函数、对字段中外部类的引用(如果您有非静态内部类)。

The oddity isn't in getDeclaredMethods() - it's in the class file for B , with a body that just calls super.foo() .奇怪的地方不在getDeclaredMethods() - 它在B的类文件中,其主体仅调用super.foo()

I don't fully understand it, but it appears to be related to foo() being a public method declared in a package-private superclass.我不完全理解它,但它似乎与foo()是在包私有超类中声明的公共方法有关。

Some test cases:一些测试用例:

  • A package-private, foo public (as per question): method is generated in B A package-private, foo public (as per question): 方法在B生成
  • A package-private, foo package-private: method isn't generated in B A package-private, foo package-private: 方法不在B生成
  • A public, foo public: method isn't generated in B A public, foo public: 方法不在B生成
  • A public, foo package-private: method isn't generated in B A public, foo package-private: 方法不在B生成

I suspect the idea is that a third class in a different package can't "see" A , but A.foo() is still public , so it should (?) be accessible through B .我怀疑这个想法是不同包中的第三个类不能“看到” A ,但A.foo()仍然是public ,所以它应该 (?) 可以通过B访问。 In order to make it accessible, B needs to "redeclare" it.为了使其可访问, B需要“重新声明”它。

It's not clear to me that this is actually correct - the (?) above.我不清楚这实际上是正确的 - 上面的(?)。 JLS 6.6.1 states (emphasis mine): JLS 6.6.1 声明(强调我的):

A member (class, interface, field, or method) of a reference type, or a constructor of a class type, is accessible only if the type is accessible and the member or constructor is declared to permit access引用类型的成员(类、接口、字段或方法)或类类型的构造函数,只有在该类型可访问且成员或构造函数声明为允许访问时才可访问

But this code is allowed in a different package:但是这个代码允许在不同的包:

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

看起来您发现了一个尚未修复的错误

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM