开发者

How to call a superclass method using Java reflection

I have two classes:

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

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

I have an instance of B. How do I call A.method() from b? Basically, the same effect as calling super.method() from B.

B b = new B();
Class<?> superclass = b.getClass().getSuperclass();
Method meth开发者_JAVA技巧od = superclass.getMethod("method", ArrayUtils.EMPTY_CLASS_ARRAY);
Object value = method.invoke(obj, ArrayUtils.EMPTY_OBJECT_ARRAY);

But the above code will still invoke B.method().


If you are using JDK7, you can use MethodHandle to achieve this:

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";
  }
}


Building on @java4script’s answer, I noticed that you get an IllegalAccessException if you try to do this trick from outside the subclass (i.e., where you would normally be calling super.toString() to begin with). The in method allows you to bypass this only in some cases (such as when you are calling from the same package as Base and Sub). The only workaround I found for the general case is an extreme (and clearly nonportable) hack:

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

printing

Sub
Base


It's not possible. Method dispatching in java always considers the run-time type of the object, even when using reflection. See the javadoc for Method.invoke; in particular, this section:

If the underlying method is an instance method, it is invoked using dynamic method lookup as documented in The Java Language Specification, Second Edition, section 15.12.4.4; in particular, overriding based on the runtime type of the target object will occur.


You can't, you'll need an instance of the super class because of the way methods dispatching works in Java.

You could try something like this:

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[]{}  );

    }
}

But most likely, it doesn't make sense, because you'll loose the data in b.


You can't do that. It would mean polymorphism is not working.

You need an instance of A. You can create one by superclass.newInstance() and then transfer all fields with something like BeanUtils.copyProperties(..) (from commons-beanutils). But that's a 'hack' - you should instead fix your design so that you don't need that.


I don't know how to do it in the case when You want to do the trick for included libraries, because the reflection doesn't work, but for my own code I would do this simple workaround:

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

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

    public Object methodSuper() {
        return super.method();
    }
}

For simple cases this is OK, for some automatic invocation not so much. For instance, when You have a chain

A1 super A2 super A3 ... super An 

of inheriting classes, all overriding a method m. Then invoking m from A1 on an instance of An would require too much bad coding :-)


You can create a byte code sequence using a different this pointer before calling invokespecial.

Calling the super.toString() method of any object is like:

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

This way creating an anonymous class containing the necessary object is possible.


If you can't modify either class, you need to use reflection to use method handles. You need to use invokespecial calls to call super methods which can be done with MethodHandles. The MethodHandles.lookup() method creates a Lookup instance with the caller class and only permits special calls from that class. To call super methods from any class, get the constructor of Lookup:

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

Create an instance of it with class A or B passed as the parameter, and use unreflect with the reflected method object which already points to A internally (or use 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);

Note that the class specified in the constructor and the last argument of unreflectSpecial/findSpecial must be the same, although it doesn't matter which class it is as long as it's a subclass of A (or A itself).

The resulting MethodHandle ignores any overrides so it will always call the method belonging to the original Method object (A in this case) or the class specified as the first argument of findSpecial. When invoking the method, pass the B object as the first parameter, and any additional arguments:

Object ret = mHandle.invoke(b);
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜