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

j'ai une classe mal conçue dans un tiers JAR et j'ai besoin d'accéder à l'un de ses privé champs. Exemple, Pourquoi devrais-je choisir un domaine privé est-ce nécessaire?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

397
demandé sur Steve Chambers 2009-07-28 23:20:00

10 réponses

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

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

EDIT : comme a été commenté par aperkins , à la fois l'accès au champ, le réglage comme accessible et la récupération de la valeur tout jeter Exception s, bien que le seul vérifié exceptions, vous devez être conscient de sont commentés ci-dessus.

le NoSuchFieldException serait jeté si vous demandiez un champ avec un nom qui ne correspond pas à un champ déclaré.

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

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

les RuntimeException s qui peuvent être lancés sont soit SecurityException s (si les SecurityManager ne vous permettra pas de changer l'accessibilité d'un champ), ou IllegalArgumentException s, si vous essayez d'accéder au champ sur un objet qui n'est pas du type de classe du champ:

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

Essayer FieldUtils à partir de apache commons-lang3:

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

la réflexion n'est pas la seule façon de résoudre votre problème (qui est d'accéder à la fonctionnalité privée/le comportement d'une classe/composante)

une autre solution consiste à extraire la classe du .jar, décompiler à l'aide de (dis) Jode ou Jad , modifiez le champ (ou ajouter un accesseur), et de recompiler contre l'original .pot. Ensuite, mettre le nouveau .classe devant le .jar dans le classpath, ou réinsérez-la dans le .jar . (le pot utilitaire vous permet d'extraire et de le réinsérer à un existant .jar)

comme indiqué ci-dessous, cela résout la question plus large de l'accès/changement d'état privé plutôt que de simplement accéder/changer un champ.

cela exige que le .jar ne soit pas signé, bien sûr.

23
répondu 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é comme un effet secondaire de la conception de la langue. Si vous avez ou non un getter pour le champ, vous pouvez utiliser

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

en utilisant le Reflection in Java vous pouvez accéder à tous les champs et méthodes private/public d'une classe à une autre .Mais selon le Oracle documentation dans la section drawbacks ils ont recommandé que:

" depuis réflexion permet au code d'effectuer des opérations qui seraient illégales dans le code non réflectif, comme l'accès à des champs privés et méthodes, 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é. Code réfléchissant casse les abstractions et peut donc changer le comportement avec des mises à niveau de la plate-forme"

voici le code snapts suivant pour démontrer les concepts de base de 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 ça aidera.

7
répondu 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 gestionnaire de sécurité vous le permettra).

cela dit, si cette classe est si mal conçue qu'elle vous fait recourir à une telle hackery, peut-être que vous devriez chercher une alternative. Je suis sûr que ce petit piratage pourrait vous faire gagner quelques heures maintenant, mais combien cela vous coûtera-t-il plus tard?

5
répondu Laurence Gonsalves 2009-07-28 19:36:58

utilisez le cadre D'optimisation Java de Suot pour modifier directement le bytecode. http://www.sable.mcgill.ca/soot/

Suot est complètement écrit en Java et fonctionne avec de nouvelles versions Java.

4
répondu pcpratts 2010-01-19 14:42:51

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
répondu Luke Hutchison 2017-02-13 09:08:11

juste une note supplémentaire au sujet de la réflexion: j'ai observé dans certains cas spéciaux, lorsque plusieurs classes avec le même nom existent dans différents paquets, que la réflexion telle qu'elle est utilisée dans la réponse du haut peut ne pas choisir la bonne classe de l'objet. Donc, si vous savez quel est le package.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 la classe qui n'a pas de travail pour moi)

1
répondu xtof54 2015-09-09 07:59:42

si vous utilisez le ressort, ReflectionTestUtils fournit quelques outils pratiques qui aident ici avec un effort minimal. Il est décrit comme étant " pour une utilisation dans les scénarios de test d'unité et d'intégration " . Il existe également une classe similaire appelée Réflectionutils mais celle - ci est décrite comme "réservé à 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
répondu Steve Chambers 2017-11-08 12:56:01