Come leggere il valore di un campo privato da una class diversa in Java?

Ho una class mal progettata in un JAR terze parti e devo accedere a uno dei suoi campi privati . Ad esempio, perché dovrei scegliere un campo privato è necessario?

 class IWasDesignedPoorly { private Hashtable stuffIWant; } IWasDesignedPoorly obj = ...; 

Come posso usare la riflessione per ottenere il valore di stuffIWant ?

Per accedere ai campi privati, è necessario recuperarli dai campi dichiarati della class e renderli accessibili:

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

EDIT : come è stato commentato da aperkins , sia l’accesso al campo, che l’impostazione come accessibile e il recupero del valore genereranno tutte le Exception , anche se le uniche eccezioni controllate di cui bisogna essere consapevoli sono commentate sopra.

La NoSuchFieldException verrebbe generata se si richiedesse un campo con un nome che non corrispondeva a un campo dichiarato.

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

L’ IllegalAccessException verrebbe generata se il campo non fosse accessibile (ad esempio, se è privato e non è stato reso accessibile tramite la f.setAccessible(true) mancante.

I RuntimeException che possono essere lanciati sono SecurityException s (se SecurityManager della JVM non ti consente di modificare l’accessibilità di un campo), o IllegalArgumentException s, se provi ad accedere al campo su un object diverso dal tipo della class del campo:

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

Prova FieldUtils da apache commons-lang3:

 FieldUtils.readField(object, fieldName, true); 

Reflection non è l’unico modo per risolvere il problema (ovvero accedere alla funzionalità / comportamento privato di una class / componente)

Una soluzione alternativa è estrarre la class da .jar, decompilarla usando (say) Jode o Jad , cambiare il campo (o aggiungere una accessor) e ricompilarlo con il file .jar originale. Quindi posiziona la nuova class .class davanti a .jar nel classpath, o reinseriscila nel file .jar . (l’utilità jar consente di estrarre e reinserire in un file .jar esistente)

Come indicato di seguito, questo risolve il problema più ampio di accesso / modifica dello stato privato piuttosto che semplicemente l’accesso / modifica di un campo.

Ciò richiede che il .jar non sia firmato, ovviamente.

Un’altra opzione che non è stata ancora menzionata: usa Groovy . Groovy ti consente di accedere a variabili di istanza private come effetto collaterale del design della lingua. Che tu abbia o meno un getter per il campo, puoi semplicemente usarlo

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

Utilizzando Reflection in Java è ansible accedere a tutti i campi e metodi private/public di una class all’altra. Tuttavia, come da documentazione Oracle negli svantaggi della sezione, hanno raccomandato che:

“Poiché la riflessione consente al codice di eseguire operazioni che sarebbero illegali in codice non riflettente, come l’accesso a campi e metodi privati, l’uso della riflessione può provocare effetti collaterali imprevisti che possono rendere il codice non funzionale e distruggere la portabilità. rompe le astrazioni e quindi può cambiare il comportamento con gli aggiornamenti della piattaforma “

ecco gli snapshot di codice seguenti per dimostrare i concetti di base di 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())); } } } 

Spero che ti sarà d’aiuto.

Come menziona oxbow_lakes, puoi usare il reflection per aggirare le restrizioni di accesso (presumendo che SecurityManager ti consenta).

Detto questo, se questa class è progettata così male da farti ricorrere a questo tipo di hackery, forse dovresti cercare un’alternativa. Certo, questo piccolo trucco potrebbe farti risparmiare qualche ora, ma quanto ti costerà?

Utilizzare il framework Ottimizzazione Java di Fuliggine per modificare direttamente il bytecode. http://www.sable.mcgill.ca/soot/

Fuliggine è completamente scritta in Java e funziona con nuove versioni di Java.

Devi fare quanto segue:

 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); } 

Solo una nota aggiuntiva sulla riflessione: ho osservato in alcuni casi speciali, quando esistono diverse classi con lo stesso nome in pacchetti diversi, che il riflesso usato nella risposta superiore potrebbe non riuscire a scegliere la class corretta dall’object. Quindi, se sai qual’è il package.class dell’object, allora è meglio accedere ai valori del suo campo privato come segue:

 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); 

(Questa è la class di esempio che non funzionava per me)

Se si utilizza Spring, ReflectionTestUtils fornisce alcuni strumenti utili che aiutano in questo modo con il minimo sforzo. È descritto come “da utilizzare in scenari di test di unità e integrazione” . Esiste anche una class simile chiamata ReflectionUtils ma che è descritta come “Destinata solo ad uso interno” – vedi questa risposta per un’interpretazione di cosa significa.

Per indirizzare l’esempio pubblicato:

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