web-dev-qa-db-fra.com

Comment appeler une méthode de superclasse en utilisant Java réflexion

J'ai deux classes.

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

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

J'ai une instance de B. Comment appeler A.method () à partir de b? Fondamentalement, le même effet que d'appeler super.method () à partir de 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);

Mais le code ci-dessus invoquera toujours B.method ()

42
Ted

Si vous utilisez JDK7, vous pouvez utiliser MethodHandle pour y parvenir:

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";
  }
}
27
java4script

Ce n'est pas possible. Répartition des méthodes dans Java considère toujours le type d'exécution de l'objet, même lors de l'utilisation de la réflexion. Voir javadoc pour Method.invoke ; en particulier, cette section:

Si la méthode sous-jacente est une méthode d'instance, elle est invoquée à l'aide de la recherche de méthode dynamique comme indiqué dans The Java Language Specification, Second Edition, section 15.12.4.4; en particulier, la substitution basée sur le type d'exécution de l'objet cible se produira.

10
Greg

En m'appuyant sur la réponse de @ Java4script, j'ai remarqué que vous obtenez un IllegalAccessException si vous essayez de faire cette astuce depuis à l'extérieur la sous-classe (c'est-à-dire, où vous appelleriez normalement super.toString() pour commencer). La méthode in vous permet de contourner cela uniquement dans certains cas (comme lorsque vous appelez à partir du même package que Base et Sub). La seule solution de contournement que j'ai trouvée pour le cas général est un piratage extrême (et clairement non 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()));
    }
}

impression

Sub
Base
7
Jesse Glick

Tu ne peux pas faire ça. Cela signifierait que le polymorphisme ne fonctionne pas.

Vous avez besoin d'une instance de A. Vous pouvez en créer un par superclass.newInstance() puis transférer tous les champs avec quelque chose comme BeanUtils.copyProperties(..) (à partir de commons-beanutils). Mais c'est un `` hack '' - vous devriez plutôt corriger votre conception afin de ne pas en avoir besoin.

4
Bozho

Vous ne pouvez pas, vous aurez besoin d'une instance de la super classe en raison de la façon dont la répartition des méthodes fonctionne en Java.

Vous pouvez essayer quelque chose comme ceci:

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[]{}  );

    }
}

Mais très probablement, cela n'a pas de sens, car vous perdrez les données dans b.

4
OscarRyz

Je ne sais pas comment le faire dans le cas où vous voulez faire l'astuce pour les bibliothèques incluses, car la réflexion ne fonctionne pas, mais pour mon propre code, je ferais cette solution simple:

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

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

    public Object methodSuper() {
        return super.method();
    }
}

Pour les cas simples, c'est OK, pour certains appels automatiques pas tellement. Par exemple, lorsque vous avez une chaîne

A1 super A2 super A3 ... super An 

d'hériter de classes, toutes remplaçant une méthode m. Ensuite, invoquer m de A1 sur une instance de An nécessiterait trop de mauvais codage :-)

2
reggie_7

Vous pouvez créer une séquence de code octet en utilisant un autre pointeur this avant d'appeler invokespecial .

L'appel de la méthode super.toString () de n'importe quel objet est comme:

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

De cette façon, la création d'une classe anonyme contenant l'objet nécessaire est possible.

0
Martin Kersten