Как вызвать метод суперкласса с использованием отражения Java
У меня есть два класса.
public class A {
public Object method() {...}
}
public class B extends A {
@Override
public Object method() {...}
}
У меня есть экземпляр B. Как мне называть A.method() из b? В принципе, тот же эффект, что и вызов super.method() из 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()
Ответы
Ответ 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";
}
}
Ответ 2
Это невозможно. Метод диспетчеризации в java всегда учитывает тип времени выполнения объекта, даже при использовании отражения. См. javadoc для Method.invoke; в частности, этот раздел:
Если базовый метод является instance, он вызывается с использованием динамический поиск метода, как описано в Спецификация языка Java, Второе издание, раздел 15.12.4.4; в особенно, основываясь на Тип выполнения целевого объекта будет происходят.
Ответ 3
Создав ответ на @java4scripts, я заметил, что вы получаете IllegalAccessException
, если пытаетесь сделать этот трюк из-за пределов подкласса (т.е. обычно вы вызываете super.toString()
). Метод in
позволяет обойти это только в некоторых случаях (например, при вызове из того же пакета, что и Base
и 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()));
}
}
Печать
Sub
Base
Ответ 4
Вы не можете этого сделать. Это означает, что полиморфизм не работает.
Вам нужен экземпляр A
. Вы можете создать его на superclass.newInstance()
, а затем перенести все поля с помощью BeanUtils.copyProperties(..)
(from commons-beanutils). Но это "взломать" - вы должны исправить свой дизайн, чтобы вам это не понадобилось.
Ответ 5
Вы не можете, вам понадобится экземпляр суперкласса из-за того, как методы dispatching работают в 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
.
Ответ 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. Тогда при вызове m из A1 на экземпляр An потребуется слишком много плохого кодирования: -)
Ответ 7
В последнее время Google не появился с новым инструментом Gradle. Поэтому он работает только в Android, но слушайте:
Чтобы описать проблему более подробно, я бы сказал:
У меня есть несколько сценариев, в которых суперкласс может иметь или не иметь осуществил конкретный метод, и там, где я хотел бы назвать метод суперкласса с отражением.
Вы можете решить эту проблему, используя gradle flavors. Для каждого сценария создайте другой вкус вашего приложения и решите, хотите ли вы назвать супер-метод или нет.
Например, если метод суперкласса был введен только в api 13, создайте аромат для api 13+ и вызовите только метод суперкласса.
Это решение устраняет необходимость использования рефлексии для вызова метода суперкласса, предлагая многострочное представление приложения.
Ответ 8
Вы можете создать последовательность байтовых кодов, используя другой этот указатель, прежде чем вызывать invokespecial.
Вызов метода super.toString() любого объекта похож:
ALOAD X ;X = slot of object reference of the object to access
INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String;
POP
Таким образом, возможно создание анонимного класса, содержащего необходимый объект.