【问题标题】:How to call a superclass method using Java reflection如何使用 Java 反射调用超类方法
【发布时间】:2011-07-21 15:38:41
【问题描述】:

我有两个班级:

public class A {
    public Object method() {...}
}

public class B extends A {
    @Override
    public Object method() {...}
}

我有一个B 的实例。如何从b 呼叫A.method()?基本上和从B调用super.method()的效果一样。

B b = new B();
Class<?> superclass = b.getClass().getSuperclass();
Method method = superclass.getMethod("method", ArrayUtils.EMPTY_CLASS_ARRAY);
Object value = method.invoke(obj, ArrayUtils.EMPTY_OBJECT_ARRAY);

但上面的代码仍然会调用B.method()

【问题讨论】:

    标签: java reflection overriding superclass


    【解决方案1】:

    如果你使用的是JDK7,你可以使用MethodHandle来实现:

    public class Test extends Base {
      public static void main(String[] args) throws Throwable {
        MethodHandle h1 = MethodHandles.lookup().findSpecial(Base.class, "toString",
            MethodType.methodType(String.class),
            Test.class);
        MethodHandle h2 = MethodHandles.lookup().findSpecial(Object.class, "toString",
            MethodType.methodType(String.class),
            Test.class);
        System.out.println(h1.invoke(new Test()));   // outputs Base
        System.out.println(h2.invoke(new Test()));   // outputs Base
      }
    
      @Override
      public String toString() {
        return "Test";
      }
    
    }
    
    class Base {
      @Override
      public String toString() {
        return "Base";
      }
    }
    

    【讨论】:

    • 你应该。不能调用 grandfather 方法。甚至通过字节码操作。
    • 这仅在类扩展另一个时有效。有没有办法在任何课程中做到这一点?
    • 如前所述,h2.invoke 不起作用(行为类似于 h1),但仍然是一个非常有用的 hack。
    • 如果使用反射获取MethodHandles.Lookup.IMPL_LOOKUP,则可以调用祖父方法,该方法不执行访问检查。演示:gist.github.com/nallar/2ac088d93fb411bd7240c9dd6c7cdc7f
    【解决方案2】:

    这是不可能的。 java中的方法调度总是考虑对象的运行时类型,即使使用反射也是如此。见javadoc for Method.invoke;特别是本节:

    如果底层方法是 实例方法,它被调用使用 动态方法查找,如记录在 Java 语言规范, 第二版,第 15.12.4.4 节;在 特别是,基于 目标对象的运行时类型将 发生。

    【讨论】:

    【解决方案3】:

    基于@java4script 的回答,我注意到如果您尝试从子类外部 执行此技巧(即,您通常会调用super.toString() 开始的地方),您会得到IllegalAccessException和)。 in 方法只允许您在某些情况下绕过它(例如,当您从与BaseSub 相同的包中调用时)。对于一般情况,我发现的唯一解决方法是极端(并且显然不可移植)黑客:

    package p;
    public class Base {
        @Override public String toString() {
            return "Base";
        }
    }
    
    package p;
    public class Sub extends Base {
        @Override public String toString() {
            return "Sub";
        }
    }
    
    import p.Base;
    import p.Sub;
    import java.lang.invoke.MethodHandle;
    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodType;
    import java.lang.reflect.Field;
    public class Demo {
        public static void main(String[] args) throws Throwable {
            System.out.println(new Sub());
            Field IMPL_LOOKUP = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
            IMPL_LOOKUP.setAccessible(true);
            MethodHandles.Lookup lkp = (MethodHandles.Lookup) IMPL_LOOKUP.get(null);
            MethodHandle h1 = lkp.findSpecial(Base.class, "toString", MethodType.methodType(String.class), Sub.class);
            System.out.println(h1.invoke(new Sub()));
        }
    }
    

    打印

    Sub
    Base
    

    【讨论】:

    • 非常聪明的解决方案,虽然很脏。
    • 更通用的解决方案是:lkp.findSpecial(Base.class, "toString", MethodType.methodType(String.class), Base.class)。否则它将被限制为 Sub 类型的实例或 Sub 的子类型的实例。
    【解决方案4】:

    你不能,你需要一个超类的实例,因为 Java 中方法分派的工作方式。

    你可以试试这样的:

    import java.lang.reflect.*;
    class A {
        public void method() {
            System.out.println("In a");
        }
    }
    class B extends A {
        @Override
        public void method() {
            System.out.println("In b");
        }
    }
    class M {
        public static void main( String ... args ) throws Exception {
            A b = new B();
            b.method();
    
            b.getClass()
             .getSuperclass()
             .getMethod("method", new Class[]{} )
             .invoke(  b.getClass().getSuperclass().newInstance() ,new Object[]{}  );
    
        }
    }
    

    但很可能,这没有意义,因为您会丢失b 中的数据。

    【讨论】:

    • 但是 super.method() 是如何工作的呢?我相信JVM同时拥有A.method和B.method字节码,如果B.method调用super.method(),它会选择调用A.method()。
    • 这是字节码级别的“特殊”指令(就像构造函数一样),似乎无法通过反射获得。指令是“invokeSpecial”参见:java.sun.com/docs/books/jvms/second_edition/html/…
    【解决方案5】:

    你不能那样做。这意味着多态性不起作用。

    您需要一个A 的实例。您可以通过superclass.newInstance() 创建一个,然后使用BeanUtils.copyProperties(..)(来自commons-beanutils)之类的内容传输所有字段。但这是一个“黑客”——你应该改正你的设计,这样你就不需要了。

    【讨论】:

    • 但是 super.method() 是如何工作的呢?我相信JVM同时拥有A.method和B.method字节码,如果B.method调用super.method(),它会选择调用A.method()。
    • @Ted super 就是这样工作的,但你只能在子类中使用它。
    【解决方案6】:

    如果您想对包含的库执行此操作,我不知道该怎么做,因为反射不起作用,但是对于我自己的代码,我会做这个简单的解决方法:

    public class A {
        public Object method() {...}
    }
    
    public class B extends A {
        @Override
        public Object method() {...}
    
        public Object methodSuper() {
            return super.method();
        }
    }
    

    对于简单的情况,这是可以的,对于一些自动调用来说,就没有那么多了。例如,当你有一条链时

    A1 super A2 super A3 ... super An 
    

    的继承类,都覆盖了一个方法m。然后在 An 的实例上从 A1 调用 m 将需要太多糟糕的编码:-)

    【讨论】:

    • 这对您没有帮助,当您必须使用反射时(例如,因为您运行的平台的早期版本中不存在 methodSuper()。)
    【解决方案7】:

    您可以在调用invokespecial 之前使用不同的this 指针创建字节码序列。

    调用任何对象的 super.toString() 方法是这样的:

    ALOAD X ;X = slot of object reference of the object to access
    INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String;
    POP
    

    这样创建一个包含必要对象的匿名类是可能的。

    【讨论】:

      【解决方案8】:

      如果你不能修改任何一个类,你需要使用反射使用方法句柄。您需要使用 invokespecial 调用来调用可以使用 MethodHandles 完成的超级方法。 MethodHandles.lookup() 方法使用调用者类创建一个 Lookup 实例,并且只允许来自该类的特殊调用。要从任何类调用超方法,请获取 Lookup 的构造函数:

      var lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
      lookupConstructor.setAccessible(true);
      

      用类 A 或 B 作为参数创建它的实例,并使用 unreflect 与内部已经指向 A 的反射方法对象(或使用 findSpecial):

      var lookup = lookupConstructor.newInstance(B.class);
      
      var method = A.class.getMethod("method");
      var mHandle = lookup.unreflectSpecial(method, B.class);
      // OR
      var mHandle = lookup.findSpecial(A.class, "method", MethodType.methodType(returnType), B.class);
      

      请注意,构造函数中指定的类和 unreflectSpecial/findSpecial 的最后一个参数必须相同,尽管它是哪个类并不重要,只要它是 A 的子类(或 A 本身)即可。

      生成的 MethodHandle 忽略任何覆盖,因此它将始终调用属于原始 Method 对象(在本例中为 A)或指定为 findSpecial 的第一个参数的类的方法。调用该方法时,将 B 对象作为第一个参数传递,以及任何其他参数:

      Object ret = mHandle.invoke(b);
      

      【讨论】:

        猜你喜欢
        • 2015-10-06
        • 1970-01-01
        • 2011-06-26
        • 1970-01-01
        • 1970-01-01
        • 2023-03-30
        • 2011-06-21
        • 1970-01-01
        相关资源
        最近更新 更多