如何使用Java反射调用超类方法

时间:2011-03-23 20:48:56

标签: java reflection override superclass

我有两节课。

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

8 个答案:

答案 0 :(得分:27)

如果您使用的是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";
  }
}

答案 1 :(得分:10)

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

  

如果基础方法是   实例方法,使用它调用   动态方法查找,如中所述   Java语言规范,   第二版,第15.12.4.4节;在   特别是,基于的压倒一切   目标对象的运行时类型   发生。

答案 2 :(得分:7)

在@ java4script的答案的基础上,我注意到如果你尝试从外部子类(即你通常会调用{{1的地方)做这个技巧,你会得到一个IllegalAccessException开始)。 super.toString()方法仅允许您在某些情况下绕过此功能(例如,当您使用与inBase相同的程序包进行呼叫时)。我发现的一般案例的唯一解决方法是极端(并且明显不可移植)黑客攻击:

Sub

印刷

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

答案 3 :(得分:4)

你做不到。这意味着多态性不起作用。

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

答案 4 :(得分: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中的数据。

答案 5 :(得分:2)

我不知道如何在你想要为包含库做技巧的情况下这样做,因为反射不起作用,但对于我自己的代码,我会做这个简单的解决方法:

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将需要太多错误的编码: - )

答案 6 :(得分:0)

在调用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

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

答案 7 :(得分:0)

如果您不能修改任何一个类,则需要使用反射使用方法句柄。您需要使用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);