49

我有两节课:

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

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

我有一个B. 我该如何A.method()打电话bsuper.method()基本上,与调用from的效果相同B

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

4

8 回答 8

33

如果您使用的是 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";
  }
}
于 2013-03-28T04:46:45.373 回答
10

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

印刷

Sub
Base
于 2014-08-08T20:55:47.877 回答
10

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

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

于 2011-03-23T20:57:51.377 回答
3

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

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

于 2011-03-23T20:52:43.953 回答
3

你不能,因为方法分派在 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.

于 2011-03-23T20:53:36.787 回答
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 将需要太多糟糕的编码:-)

于 2011-08-17T22:26:23.927 回答
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

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

于 2016-08-12T20:05:33.160 回答
-1

如果不能修改任一类,则需要使用反射使用方法句柄。您需要使用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);
于 2020-07-31T16:38:45.237 回答