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

482

J'ai une classe mal conçue dans une tierce partie JARet j'ai besoin d'accéder à l'un de ses champs privés . Par exemple, pourquoi devrais-je avoir besoin de 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?

Frank Krueger
la source

Réponses:

668

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 l'a commenté aperkins , à la fois accéder au champ, le définir comme accessible et récupérer la valeur peut lancer Exceptions, bien que les seules exceptions vérifiées dont vous devez être conscient soient commentées ci-dessus.

Le NoSuchFieldExceptionserait levé si vous demandiez un champ par un nom qui ne correspondait pas à un champ déclaré.

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

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

Les RuntimeExceptions qui peuvent être levés sont soit SecurityExceptions (si la machine virtuelle Java SecurityManagerne vous permet pas de modifier l'accessibilité d'un champ), soit IllegalArgumentExceptions, si vous essayez d'accéder au champ sur un objet qui n'est pas du type de la classe du champ:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
oxbow_lakes
la source
4
pouvez-vous expliquer les commentaires d'exception? le code s'exécutera mais lèvera des exceptions? ou le code peut lever des exceptions?
Nir Levy
1
@Nir - Non - selon toute vraisemblance, le code fonctionnera très bien (car le SecurityManager par défaut permettra de modifier l'accessibilité des champs) - mais vous devez gérer les exceptions vérifiées (soit les intercepter, soit les déclarer comme étant renvoyées ). J'ai un peu modifié ma réponse. Il pourrait être bon pour vous d'écrire un petit cas de test pour jouer et voir ce qui se passe
oxbow_lakes
3
Désolé, cette réponse me déroute. Peut-être montrer un exemple de gestion d'exception typique. Il semble que les exceptions se produisent lorsque les classes sont mal connectées. L'exemple de code donne l'impression que les exceptions seraient levées sur les llnes correspondantes.
LaFayette
2
getDeclaredField()ne trouve pas les champs s'ils sont définis dans les classes parentes - vous devez également parcourir la hiérarchie des classes parentes, en appelant getDeclaredField()chacune, jusqu'à ce que vous trouviez une correspondance (après laquelle vous pouvez appeler setAccessible(true)) ou que vous atteigniez Object.
Luke Hutchison
1
@legend, vous pouvez installer un gestionnaire de sécurité pour interdire cet accès. Depuis Java 9, un tel accès n'est pas censé fonctionner au-delà des frontières du module (à moins qu'un module ne soit ouvert explicitement), bien qu'il y ait une phase de transition concernant l'application des nouvelles règles. En plus de cela, nécessiter des efforts supplémentaires comme la réflexion a toujours fait une différence pour les non- privatechamps. Il empêche l'accès accidentel.
Holger du
159

Essayez FieldUtilsavec apache commons-lang3:

FieldUtils.readField(object, fieldName, true);
yegor256
la source
76
Je suis convaincu que vous pourriez résoudre la plupart des problèmes du monde en regroupant quelques méthodes de commons-lang3.
Cameron
@yegor pourquoi java permet cela? signifie pourquoi java permet d'accéder aux membres privés?
Asif Mushtaq
1
@ yegor256 Je peux toujours accéder aussi aux membres privés C # et C ++ .. !! Alors? tous les créateurs de langues sont faibles?
Asif Mushtaq
3
@UnKnown java ne fonctionne pas. Il y a deux choses différentes - langage java et java en tant que machine virtuelle java. Ce dernier fonctionne en bytecode. Et il y a des bibliothèques pour manipuler cela. Ainsi, le langage java ne vous permet pas d'utiliser des champs privés en dehors de la portée ou ne permet pas de muter les références finales. Mais il est possible de le faire en utilisant l'outil. Ce qui se trouve être à l'intérieur de la bibliothèque standard.
evgenii
3
@UnKnown l'une des raisons est que Java n'a pas d'accès "ami" pour permettre l'accès au champ uniquement par une infrastructure d'infrastructure comme DI, ORM ou sérialiseur XML / JSON. De telles infrastructures doivent avoir accès aux champs d'objet pour sérialiser ou initialiser correctement l'état interne d'un objet, mais vous pouvez toujours avoir besoin d'une mise en application appropriée de l'encapsulation au moment de la compilation pour votre logique métier.
Ivan Gammel
26

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 autre solution consiste à extraire la classe du .jar, à la décompiler en utilisant (disons) Jode ou Jad , à modifier le champ (ou à ajouter un accesseur) et à le recompiler avec le .jar d'origine. Mettez ensuite la nouvelle .class devant le .jardans le chemin de classe, ou réinsérez-la dans le .jar. (l'utilitaire jar vous permet d'extraire et de réinsérer dans un .jar existant)

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

Il faut pour cela de .jarne pas être signé, bien sûr.

Brian Agnew
la source
36
Cette manière sera assez pénible pour un simple champ.
Valentin Rocher
1
Je ne suis pas d'accord. Il vous permet non seulement d'accéder à votre domaine, mais également de changer de classe si nécessaire si l'accès au champ s'avère insuffisant.
Brian Agnew
4
Ensuite, vous devez recommencer. Que se passe-t-il si le pot reçoit une mise à jour et que vous utilisez la réflexion pour un champ qui n'existe plus? C'est exactement le même problème. Il vous suffit de le gérer.
Brian Agnew
4
Je suis étonné de voir combien cela fait l'objet d'un vote négatif étant donné a) il est mis en évidence comme une alternative pratique b) il répond aux scénarios dans lesquels la modification de la visibilité d'un champ n'est pas suffisante
Brian Agnew
2
@BrianAgnew c'est peut-être juste sémantique mais si nous nous en tenons à la question (utiliser la réflexion pour lire un champ privé), ne pas utiliser la réflexion est tout de suite une auto contradiction. Mais je suis d'accord que vous donnez accès au champ ... mais le champ n'est toujours plus privé, donc encore une fois nous ne nous en tenons pas au "lire un champ privé" de la question. Sous un autre angle, la modification .jar peut ne pas fonctionner dans certains cas (jar signé), doit être effectuée chaque fois que le jar est mis à jour, nécessite une manipulation prudente du chemin de classe (que vous ne pouvez pas contrôler complètement si vous êtes exécuté dans un conteneur d'application) etc.
Remi Morin
18

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 comme effet secondaire de la conception du langage. Que vous ayez ou non un getter pour le terrain, vous pouvez simplement utiliser

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
lucas
la source
4
L'OP a spécifiquement demandé Java
Jochen
3
De nombreux projets Java intègrent aujourd'hui groovy. Il suffit que le projet utilise le groovy DSL de Spring et qu'ils auront groovy sur le chemin de classe par exemple. Dans ce cas, cette réponse est utile, et bien qu'elle ne réponde pas directement au PO, elle sera bénéfique pour de nombreux visiteurs.
Amir Abiri
10

En utilisant la réflexion en Java, vous pouvez accéder à tous les private/publicchamps et méthodes d'une classe à une autre. Mais selon la documentation Oracle 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 un 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 détruire la portabilité. Code réfléchissant brise les abstractions et peut donc changer de comportement avec les mises à niveau de la plate-forme "

voici des extraits de code pour illustrer les concepts de base de la réflexion

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 cela vous aidera.

Simmant
la source
5

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 fait 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?

Laurence Gonsalves
la source
3
J'ai plus de chance que ça en fait, j'utilise juste ce code pour extraire des données, après quoi je peux les remettre dans la corbeille.
Frank Krueger
2
Eh bien, dans ce cas, éloignez-vous. :-)
Laurence Gonsalves
3
Cela ne fournit pas de réponse à la question. Pour critiquer ou demander des éclaircissements à un auteur, laissez un commentaire sous son article.
Mureinik
@Mureinik - Il répond à la question, avec les mots "vous pouvez utiliser la réflexion". Il manque un exemple ou une plus grande explication ou comment, mais c'est une réponse. Votez-le si vous ne l'aimez pas.
ArtOfWarfare
4

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.

pcpratts
la source
3

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);
}
Luke Hutchison
la source
2

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

Pour traiter l'exemple publié:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Steve Chambers
la source
1
Si vous décidez d'utiliser une classe Utils d'ici Spring, vous devriez vraiment utiliser celle qui n'est pas test ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) à la place, sauf si, bien sûr, vous l'utilise en fait pour des tests unitaires.
Sync
Peut-être, bien que cette classe soit décrite comme "destinée uniquement à un usage interne". J'ai ajouté quelques informations à la réponse à ce sujet. (J'ai gardé l'exemple en utilisant ReflectionTestUtilscomme dans mon expérience, je n'ai jamais eu besoin de faire ce genre de chose dans une situation de test.)
Steve Chambers
1

Juste une note supplémentaire sur la réflexion: j'ai observé dans certains cas spéciaux, lorsque plusieurs classes du même nom existent dans des packages différents, que la réflexion telle qu'utilisée dans la première réponse peut ne pas sélectionner la classe correcte dans l'objet. Donc, si vous savez quelle est la package.class de l'objet, 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);

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

xtof54
la source
1

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

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

public class Foo {
    private String stuffIWant;
}

@JailBreakdéverrouille la foovariable locale dans le compilateur pour un accès direct à tous les membres de Foola hiérarchie.

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

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

Grâce à la jailbreak()méthode, vous pouvez accéder à n'importe quel membre de Foola hiérarchie.

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 le collecteur .

Scott
la source
1

C'est assez simple avec l'outil XrayInterface . Il suffit de définir les getters / setters manquants, par exemple

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

et xray votre pauvre projet conçu:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

En interne, cela repose sur la réflexion.

Couronne
la source
0

Essayez de contourner le problème pour le cas, le type dont vous voulez définir / obtenir des données est l'une de vos propres classes.

Créez simplement un public setter(Field f, Object value)et public Object getter(Field f)pour cela. Vous pouvez même faire quelques vérifications de sécurité par vous-même à l'intérieur de ces fonctions membres. Par exemple pour le passeur:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

Bien sûr, vous devez maintenant trouver le java.lang.reflect.Fieldfor sStringavant de définir la valeur du champ.

J'utilise cette technique dans un mappeur générique ResultSet-to-and-from-model.

Karldegen
la source