Comment lire la valeur d'un champ privé d'une classe différente en Java?


J'ai une classe mal conçue dans un JARtiers et j'ai besoin d'accéder à l'un de ses champs privés. Exemple, pourquoi devrais-je choisir le champ privé est-il nécessaire?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Comment puis-je utiliser la réflexion pour obtenir la valeur de stuffIWant?

Author: Steve Chambers, 2009-07-28

10 answers

Pour accéder aux champs privés, vous devez les obtenir à partir des champs déclarés de la classe, puis les rendre accessibles:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

EDIT : comme cela a été commenté par aperkins, les deux accès au champ, le définir comme accessible et récupérer la valeur lanceront tous Exceptions, bien que les seules exceptions cochées dont vous devez être conscient soient commentées ci-dessus.

Le {[4] } serait lancé si vous demandiez un champ par un nom qui ne correspondait pas à un champ déclaré.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

Le IllegalAccessException serait lancé si le champ n'était pas accessible (par exemple, s'il est privé et n'a pas été rendu accessible en manquant la ligne f.setAccessible(true).

Les RuntimeException qui peuvent être lancés sont soit SecurityException s (si le SecurityManager de la JVM ne vous permettra pas de modifier l'accessibilité d'un champ), soit IllegalArgumentExceptions, si vous essayez d'accéder au champ sur un objet non du type de classe du champ:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
 559
Author: oxbow_lakes, 2009-07-29 07:30:47

Essayer FieldUtils à partir de apache commons-lang3:

FieldUtils.readField(object, fieldName, true);
 114
Author: yegor256, 2014-04-25 07:40:48

La réflexion n'est pas le seul moyen de résoudre votre problème (qui consiste à accéder aux fonctionnalités/comportements privés d'une classe/d'un composant)

Une solution alternative consiste à extraire la classe du .jar, décompilez-le en utilisant (disons) Jode ou Jad, modifiez le champ (ou ajoutez un accesseur) et recompilez-le par rapport à l'original .pot. Ensuite, mettez le nouveau .classe avant le .jar dans le chemin de classe, ou réinsérez-le dans le .jar. (l'utilitaire jar vous permet d'extraire et de réinsérer un existant .jar)

Comme indiqué ci-dessous, cela résout le problème plus large de l'accès/de la modification d'un état privé plutôt que de simplement accéder/modifier un champ.

Cela nécessite que le .jar ne soit pas signé, bien sûr.

 23
Author: Brian Agnew, 2017-10-25 10:36:46

Une autre option qui n'a pas encore été mentionnée: utilisez Groovy. Groovy vous permet d'accéder à des variables d'instance privées en tant qu'effet secondaire de la conception du langage. Que vous ayez ou non un getter pour le champ, vous pouvez simplement utiliser

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
 14
Author: lucas, 2012-12-13 09:07:28

En utilisant la réflexion dans Java , vous pouvez accéder à tous les champs et méthodes private/public d'une classe à une autre .Mais comme par le Oracle documentation dans la section inconvénients ils ont recommandé que :

"Étant donné que la réflexion permet au code d'effectuer des opérations qui seraient illégales dans le code non réfléchissant, telles que l'accès à des champs et des méthodes privés, l'utilisation de la réflexion peut entraîner des effets secondaires inattendus, qui peuvent rendre le code dysfonctionnel et peut détruire la portabilité. Le code réfléchissant casse les abstractions et peut donc changer le comportement avec les mises à niveau de la plate-forme "

Voici les snapts de code suivants pour démontrer les concepts de base de la réflexion

Réflection1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Réflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

J'espère que cela vous aidera.

 7
Author: Simmant, 2014-09-25 05:09:59

Comme le mentionne oxbow_lakes, vous pouvez utiliser la réflexion pour contourner les restrictions d'accès (en supposant que votre SecurityManager vous le permettra).

Cela dit, si cette classe est si mal conçue qu'elle vous oblige à recourir à un tel piratage, vous devriez peut-être chercher une alternative. Bien sûr, ce petit hack pourrait vous faire économiser quelques heures maintenant, mais combien cela vous coûtera-t-il sur la route?

 5
Author: Laurence Gonsalves, 2009-07-28 19:36:58

Utilisez le framework d'optimisation Java Suot pour modifier directement le bytecode. http://www.sable.mcgill.ca/soot/

Suie est entièrement écrit en Java et fonctionne avec les nouvelles versions de Java.

 4
Author: pcpratts, 2010-01-19 14:42:51

, Vous devez effectuer les opérations suivantes:

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}
 2
Author: Luke Hutchison, 2017-02-13 09:08:11

Juste une note supplémentaire sur la réflexion: j'ai observé dans certains cas particuliers, lorsque plusieurs classes du même nom existent dans des paquets différents, que la réflexion utilisée dans la réponse supérieure peut ne pas choisir la classe correcte de l'objet. Donc, si vous savez quel est le paquet.classe de l'objet, alors il est préférable d'accéder à ses valeurs de champ privé comme suit:

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(C'est l'exemple de classe qui ne fonctionnait pas pour moi)

 1
Author: xtof54, 2015-09-09 07:59:42

Si vous utilisez Spring, ReflectionTestUtils fournit des outils pratiques qui aident ici avec un minimum d'effort. Il est décrit comme étant "pour une utilisation dans des scénarios de tests unitaires et d'intégration". Il existe également une classe similaire nommée ReflectionUtils mais ceci est décrit comme "Uniquement destiné à un usage interne" - voir cette réponse pour une interprétation de ce que cela signifie.

Pour répondre à l'exemple affiché:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
 0
Author: Steve Chambers, 2017-11-08 12:56:01