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
?
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
Essayer FieldUtils
à partir de apache commons-lang3:
FieldUtils.readField(object, fieldName, true);
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.
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()
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.
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?
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.
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);
}
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)
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");