sirve - superclase java
Cómo llamar a un método de superclase utilizando la reflexión de Java (7)
Tengo dos clases.
public class A {
public Object method() {...}
}
public class B extends A {
@Override
public Object method() {...}
}
Tengo una instancia de B. ¿Cómo puedo llamar a A.method () desde b? Básicamente, el mismo efecto que llamar a super.method () desde 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);
Pero el código anterior todavía invocará B.method ()
Basándome en la respuesta de @java4script, noté que obtienes una IllegalAccessException
si tratas de hacer este truco desde fuera de la subclase (es decir, donde normalmente super.toString()
llamando a super.toString()
para empezar). El método in
permite omitir esto solo en algunos casos (como cuando llama desde el mismo paquete que Base
y Sub
). La única solución alternativa que encontré para el caso general es un truco extremo (y claramente no portable):
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()));
}
}
impresión
Sub
Base
No es posible. El envío de métodos en Java siempre considera el tipo de tiempo de ejecución del objeto, incluso cuando se usa el reflejo. Vea el javadoc para Method.invoke ; en particular, esta sección:
Si el método subyacente es un método de instancia, se invoca utilizando la búsqueda de métodos dinámicos tal como se documenta en la Especificación del lenguaje Java, segunda edición, sección 15.12.4.4; en particular, se producirá una anulación basada en el tipo de tiempo de ejecución del objeto de destino.
No puede, necesitará una instancia de la superclase debido a la forma en que el envío de métodos funciona en Java.
Podrías probar algo como esto:
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[]{} );
}
}
Pero lo más probable es que no tenga sentido, porque perderá los datos en b
.
No puedes hacer eso. Significaría que el polimorfismo no está funcionando.
Necesitas una instancia de A
Puede crear uno por superclass.newInstance()
y luego transferir todos los campos con algo como BeanUtils.copyProperties(..)
(de commons-beanutils). Pero eso es un ''hack'': en su lugar, debes arreglar tu diseño para que no lo necesites.
No sé cómo hacerlo en el caso en que quieras hacer el truco para las bibliotecas incluidas, porque la reflexión no funciona, pero para mi propio código, haría esta solución simple:
public class A {
public Object method() {...}
}
public class B extends A {
@Override
public Object method() {...}
public Object methodSuper() {
return super.method();
}
}
Para casos simples, esto está bien, para algunas invocación automática no tanto. Por ejemplo, cuando tienes una cadena
A1 super A2 super A3 ... super An
de heredar clases, reemplazando todo un método m. Entonces, invocar m desde A1 en una instancia de An requeriría demasiada mala codificación :-)
Puede crear una secuencia de código de bytes usando un puntero diferente antes de invocar docs.oracle.com/javase/specs/jvms/se7/html/… .
Llamar al método super.toString () de cualquier objeto es como:
ALOAD X ;X = slot of object reference of the object to access
INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String;
POP
De esta forma, es posible crear una clase anónima que contenga el objeto necesario.
Si está utilizando JDK7, puede usar MethodHandle para lograr esto:
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";
}
}