web-dev-qa-db-fra.com

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

J'ai une classe mal conçue dans une JAR tierce et j'ai besoin d'accéder à l'un de ses champs private . Par exemple, Pourquoi devrais-je choisir un champ privé si cela est nécessaire?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

424
Frank Krueger

Pour accéder aux champs privés, vous devez les obtenir à partir des champs declare 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, accéder au champ, le définir comme accessible et récupérer la valeur jetteront tous Exceptions, bien que les seules exceptions checked que vous devez être conscients de sont commentés ci-dessus.

NoSuchFieldException serait levé si vous demandiez un champ dont le nom ne correspondait pas à un champ déclaré. 

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

IllegalAccessException serait lancé si le champ n'était pas accessible (par exemple, s'il est privé et qu'il n'a pas été rendu accessible via l'absence de la ligne f.setAccessible(true).

Les RuntimeExceptions pouvant être générés sont soit SecurityExceptions (si le SecurityManager de la machine virtuelle ne vous permet pas de modifier l'accessibilité d'un champ), soit IllegalArgumentExceptions, si vous essayez d'accéder au champ sur un objet n'appartenant pas au type de la classe du champ:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
592
oxbow_lakes

Essayez FieldUtils d'Apache commons-lang3:

FieldUtils.readField(object, fieldName, true);
124
yegor256

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

Une autre solution consiste à extraire la classe du fichier .jar, à la décompiler en utilisant (par exemple) Jode ou Jad , à modifier le champ (ou à ajouter un accesseur) et à le recompiler avec le fichier .jar d'origine. Placez ensuite la nouvelle classe. Devant 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 fichier .jar existant)

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

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

24
Brian Agnew

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 de la langue. Que vous ayez ou non un getter pour le champ, vous pouvez simplement utiliser 

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
16
lucas

En utilisant Reflection in Java, vous pouvez accéder à tous les champs et méthodes private/public d'une classe à une autre. Mais selon la Oracledocumentation de la section drawbacks, ils ont recommandé que: 

"Puisque la réflexion permet au code d'effectuer des opérations qui seraient illégales dans un code non réfléchissant, tel que l'accès à des champs et méthodes privés, l'utilisation de la réflexion peut entraîner des effets secondaires inattendus, pouvant rendre le code dysfonctionnel et détruire la portabilité Le code réfléchissant rompt les abstractions et peut donc modifier le comportement lors des mises à niveau de la plate-forme "

voici les captures de code suivantes pour illustrer les concepts de base de Reflection 

Reflection1.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");
    }

}

Reflection2.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 ça va aider.

7
Simmant

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 permette).

Cela dit, si cette classe est si mal conçue qu'elle vous oblige à recourir à un tel hackery, vous devriez peut-être chercher une alternative. Certes, ce petit bidouillage pourrait vous faire gagner quelques heures maintenant, mais combien cela vous coûtera-t-il plus tard?

5
Laurence Gonsalves

Utilisez le framework Soot Java Optimization pour modifier directement le bytecode . http://www.sable.mcgill.ca/soot/

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

4
pcpratts

Vous devez faire ce qui suit:

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
Luke Hutchison

Juste une remarque supplémentaire à propos de la réflexion: dans certains cas particuliers, lorsque plusieurs classes portant le même nom existent dans des packages différents, la réflexion utilisée dans la réponse supérieure peut ne pas réussir à sélectionner la classe correcte dans l'objet. Donc, si vous savez quelle est la classe package.class de l'objet, il est préférable d'accéder aux valeurs de son 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);

(Ceci est la classe d'exemple qui ne fonctionnait pas pour moi)

1
xtof54

Vous pouvez utiliser Manifold@JailBreak pour une réflexion Java directe et sécurisée au type:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreak déverrouille la variable locale foo dans le compilateur pour un accès direct à tous les membres de la hiérarchie de Foo.

De même, vous pouvez utiliser la méthode d'extension jailbreak () pour une utilisation unique:

foo.jailbreak().stuffIWant = "123";

Avec la méthode jailbreak(), vous pouvez accéder à n’importe quel membre de la hiérarchie de Foo.

Dans les deux cas, le compilateur résout l'accès au champ pour vous en toute sécurité, comme s'il s'agissait d'un champ public, tandis que Manifold génère un code de réflexion efficace pour vous sous le capot.

En savoir plus sur Collecteur .

0
Scott McKinney

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

Pour adresser l'exemple posté:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
0
Steve Chambers