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

发布于 2024-10-26 09:18:02 字数 592 浏览 3 评论 0 原文

我有两个类:

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

昔梦 2024-11-02 09:18:02

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

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";
  }
}
就此别过 2024-11-02 09:18:02

基于@java4script的答案,我注意到如果您尝试从子类外部(即您通常会调用的地方)执行此技巧,您会得到一个IllegalAccessException首先是 super.toString())。 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

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
书信已泛黄 2024-11-02 09:18:02

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

如果底层方法是
实例方法,使用以下方式调用
动态方法查找,如文档中所述
Java 语言规范,
第二版,第 15.12.4.4 节;在
特别的,压倒性的基于
目标对象的运行时类型将
发生。

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.

左秋 2024-11-02 09:18:02

你不能,因为 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 中的数据。

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.

り繁华旳梦境 2024-11-02 09:18:02

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

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

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.

残月升风 2024-11-02 09:18:02

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

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

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

琴流音 2024-11-02 09:18:02

您可以在调用 调用特殊

调用任何对象的 super.toString() 方法就像:

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

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

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.

幻想少年梦 2024-11-02 09:18:02

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

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

创建它的实例,并使用 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);

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

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

Object ret = mHandle.invoke(b);

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文