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


Ho una classe mal progettata in un 3rd-party JARe ho bisogno di accedere a uno dei suoi campi privati. Biru, perché dovrei avere bisogno di scegliere il campo privato è necessario?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Come posso usare reflection per ottenere il valore di stuffIWant?

Author: Steve Chambers, 2009-07-28

10 answers

Per accedere ai campi privati, è necessario ottenerli dai campi dichiarati della classe e quindi 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, impostandolo come accessibile che il recupero del valore genereranno tutti Exceptions, anche se le uniche eccezioni controllate di cui devi essere consapevole sono commentate sopra.

Il {[4] } verrà generato se hai chiesto un campo con un nome che non corrispondeva a un campo dichiarato.

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

Il IllegalAccessException verrebbe generato se il campo non fosse accessibile (ad esempio, se è privato e non è stato reso accessibile perdendo la riga f.setAccessible(true).

Le RuntimeExceptionche possono essere lanciate sono SecurityException s (se le SecurityManager della JVM non consentono di modificare l'accessibilità di un campo) o IllegalArgumentException s, se si tenta di accedere al campo su un oggetto non del tipo di classe del campo:

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

Prova FieldUtils da apache commons-lang3:

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

La riflessione non è l'unico modo per risolvere il problema (che consiste nell'accedere alla funzionalità/comportamento privato di una classe/componente)

Una soluzione alternativa è estrarre la classe dal .jar, decompilarlo usando (diciamo) Jode o Jad , cambiare il campo (o aggiungere un accessor) e ricompilarlo rispetto all'originale .vaso. Quindi metti il nuovo .classe prima del .jar nel classpath, o reinserirlo nel .jar. (l'utilità jar consente di estrarre e reinserire in un esistente .vaso)

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

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

 23
Author: Brian Agnew, 2017-10-25 10:36:46

Un'altra opzione che non è stata ancora menzionata: usa Groovy. Groovy 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 usare

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

Utilizzando il Riflessione in Java è possibile accedere a tutti i private/public campi e metodi di una classe a un'altra .Ma secondo l'Oracolo documentazione {[9] } nella sezione svantaggi hanno raccomandato che:

"Poiché reflection consente al codice di eseguire operazioni illegali nel codice non riflettente, come l'accesso a campi e metodi privati, l'uso di reflection può causare effetti collaterali imprevisti, che possono rendere il codice disfunzionale e può distruggere la portabilità. Il codice riflettente interrompe le astrazioni e quindi può cambiare il comportamento con gli aggiornamenti della piattaforma "

Ecco i seguenti snapt di codice per dimostrare i concetti di base di Reflection

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

}

Riflettenza2.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 possa aiutare.

 7
Author: Simmant, 2014-09-25 05:09:59

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

Detto questo, se questa classe è così mal progettata che ti fa ricorrere a tale hackery, forse dovresti cercare un'alternativa. Certo, questo piccolo trucco potrebbe farti risparmiare qualche ora, ma quanto ti costerà lungo la strada?

 5
Author: Laurence Gonsalves, 2009-07-28 19:36:58

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

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

 4
Author: pcpratts, 2010-01-19 14:42:51

È necessario effettuare le seguenti operazioni:

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
Author: Luke Hutchison, 2017-02-13 09:08:11

Solo una nota aggiuntiva su reflection: ho osservato in alcuni casi speciali, quando diverse classi con lo stesso nome esistono in pacchetti diversi, che reflection come usato nella risposta principale potrebbe non riuscire a scegliere la classe corretta dall'oggetto. Quindi, se si sa che cosa è il pacchetto.classe dell'oggetto, quindi è meglio accedere ai suoi valori di 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 classe di esempio che non funzionava per me)

 1
Author: xtof54, 2015-09-09 07:59:42

Se si utilizza Spring, ReflectionTestUtils fornisce alcuni strumenti utili che aiutano qui con il minimo sforzo. È descritto come "per l'uso in scenari di test di unità e integrazione". Esiste anche una classe simile denominata ReflectionUtils ma questa è descritta come "Solo per uso interno" - vedi questa risposta per un'interpretazione di ciò che significa.

Per risolvere l'esempio pubblicato:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
 0
Author: Steve Chambers, 2017-11-08 12:56:01