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


482

Ho una classe mal progettata in una terza parte JARe devo accedere a uno dei suoi campi privati . Ad esempio, perché dovrei scegliere il campo privato è necessario?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

Risposte:


668

Per accedere ai campi privati, devi ottenerli dai campi dichiarati della classe 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 accedere al campo, impostarlo come accessibile e recuperare il valore può generare Exceptions, anche se le uniche eccezioni verificate che è necessario tenere presente sono commentate sopra.

Verrà NoSuchFieldExceptiongenerato se si richiedesse un campo con un nome che non corrispondesse a un campo dichiarato.

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

Verrà IllegalAccessExceptiongenerato se il campo non fosse accessibile (ad esempio, se è privato e non è stato reso accessibile perdendo la f.setAccessible(true)linea.

Le RuntimeExceptions che possono essere lanciate sono SecurityExceptions (se le JVM SecurityManagernon ti consentono di modificare l'accessibilità di un campo), oppure IllegalArgumentExceptions, se provi ad 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

4
puoi per favore spiegare i commenti di eccezione? il codice verrà eseguito ma genererà eccezioni? o il codice può generare eccezioni?
Nir Levy,

1
@Nir - No - con ogni probabilità il codice verrà eseguito correttamente (poiché il SecurityManager predefinito consentirà di modificare l'accessibilità dei campi) - ma è necessario gestire le eccezioni verificate (catturarle o dichiararle per essere riproposte ). Ho modificato un po 'la mia risposta. Potrebbe essere utile per te scrivere un piccolo test case per giocare e vedere cosa succede
oxbow_lakes,

3
Scusa, questa risposta mi confonde. Forse mostra un esempio di gestione tipica delle eccezioni. Sembra che si verifichino eccezioni quando le classi sono collegate in modo errato. L'esempio di codice fa sembrare che le eccezioni verrebbero lanciate sui llnes corrispondenti.
LaFayette,

2
getDeclaredField()non trova i campi se sono definiti nelle classi principali - devi anche scorrere attraverso la gerarchia delle classi principali, chiamando getDeclaredField()ciascuno, fino a quando non trovi una corrispondenza (dopo la quale puoi chiamare setAccessible(true)), o raggiungi Object.
Luke Hutchison,

1
@legend puoi installare un gestore della sicurezza per vietare tale accesso. A partire da Java 9, tale accesso non dovrebbe funzionare oltre i confini del modulo (a meno che un modulo non sia aperto in modo esplicito), sebbene ci sia una fase di transizione per quanto riguarda l'applicazione delle nuove regole. Inoltre, richiedere ulteriori sforzi come Reflection ha sempre fatto la differenza per i non- privatecampi. Impedisce l'accesso accidentale.
Holger,

159

Prova FieldUtilsda apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

76
Sono convinto che potresti risolvere la maggior parte dei problemi del mondo mettendo insieme alcuni metodi di commons-lang3.
Cameron,

@yegor perché java lo consente? significa perché Java ha permesso di accedere ai membri privati?
Asif Mushtaq,

1
@ yegor256 Posso ancora accedere anche ai membri privati ​​C # e C ++ .. !! Poi? tutti i creatori di lingue sono deboli?
Asif Mushtaq,

3
@UnKnown java no. Esistono due cose diverse: java language e java come java virtual machine. Quest'ultimo opera su bytecode. E ci sono librerie per manipolarlo. Quindi il linguaggio java non ti consente di utilizzare campi privati ​​al di fuori dell'ambito o non consente di mutare i riferimenti finali. Ma è possibile farlo utilizzando lo strumento. Il che è solo all'interno della libreria standard.
Evgenii,

3
@UnKnown Uno dei motivi è che Java non ha accesso "amico" per consentire l'accesso al campo solo da un framework di infrastruttura come serializzatore DI, ORM o XML / JSON. Tali framework necessitano dell'accesso ai campi oggetto per serializzare o inizializzare correttamente lo stato interno di un oggetto, ma potrebbe essere ancora necessario applicare correttamente l'incapsulamento in fase di compilazione per la propria logica aziendale.
Ivan Gammel,

26

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

Una soluzione alternativa è quella di estrarre la classe dal .jar, decompilarla usando (diciamo) Jode o Jad , cambiare il campo (o aggiungere un accessorio) e ricompilarla con il .jar originale. Quindi metti il ​​nuovo .class davanti al .jarnel percorso di classe o reinseriscilo nel file .jar. (l'utilità jar consente di estrarre e reinserire un file .jar esistente)

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

Ciò richiede .jarovviamente di non essere firmato.


36
In questo modo sarà piuttosto doloroso solo per un campo semplice.
Valentin Rocher,

1
Non sono d'accordo. Ti permette non solo di accedere al tuo campo, ma anche di cambiare la classe se necessario se l'accesso al campo risulta non essere sufficiente.
Brian Agnew,

4
Quindi devi farlo di nuovo. Cosa succede se il vaso riceve un aggiornamento e si utilizza la riflessione per un campo che non esiste più? È esattamente lo stesso problema. Devi solo gestirlo.
Brian Agnew,

4
Sono stupito di quanto questo venga ridimensionato dato a) è evidenziato come un'alternativa pratica b) approvvigiona per scenari in cui cambiare la visibilità di un campo non è sufficiente
Brian Agnew,

2
@BrianAgnew forse è solo semantico, ma se ci atteniamo alla domanda (usare la riflessione per leggere un campo privato) non usare la riflessione è subito un'auto contraddizione. Ma sono d'accordo che fornisci l'accesso al campo ... ma il campo non è più privato, quindi di nuovo non ci atteniamo al "campo privato" della domanda. Da un altro punto di vista, la modifica .jar potrebbe non funzionare in alcuni casi (jar firmato), deve essere eseguita ogni volta che il jar viene aggiornato, richiedere un'attenta manipolazione del percorso di classe (che potrebbe non essere completamente controllato se si esegue in un contenitore di applicazione) ecc.
Remi Morin,

18

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 usare

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

4
L'OP ha specificamente richiesto Java
Jochen il

3
Molti progetti Java oggi incorporano groovy. È sufficiente che il progetto utilizzi il groovy DSL di Spring e avranno ad esempio groovy sul percorso di classe. Nel qual caso questa risposta è utile e, sebbene non risponda direttamente al PO, sarà di beneficio per molti visitatori.
Amir Abiri,

10

Utilizzando Reflection in Java è possibile accedere a tutti i private/publiccampi e i metodi di una classe per un'altra. Ma come da documentazione Oracle nella sezione svantaggi hanno raccomandato che:

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

ecco i seguenti codici 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 possa aiutare.


5

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

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


3
Sono più fortunato di quello in realtà, sto solo usando questo codice per estrarre alcuni dati, dopo di che posso buttarli di nuovo nel Cestino.
Frank Krueger,

2
Bene, in quel caso, vai via. :-)
Laurence Gonsalves il

3
Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post.
Mureinik,

@Mureinik - Risponde alla domanda, con le parole "puoi usare la riflessione". Manca un esempio o una spiegazione più ampia o come, ma è una risposta. Sottoscrivilo se non ti piace.
ArtOfWarfare il

4

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

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


3

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

2

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

Per affrontare l'esempio pubblicato:

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

1
Se decidi di utilizzare una classe Utils entro Spring, dovresti davvero utilizzare quella non test ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) invece a meno che, ovviamente, tu lo stai effettivamente usando per i test unitari.
Sincronizza l'

Forse, sebbene quella classe sia descritta come "Destinata solo per uso interno". Ho aggiunto alcune informazioni alla risposta al riguardo. (Ho tenuto l'esempio usando ReflectionTestUtilscome nella mia esperienza ho sempre avuto bisogno di fare questo genere di cose in una situazione di test.)
Steve Chambers

1

Solo una nota aggiuntiva sulla riflessione: ho osservato in alcuni casi speciali, quando esistono diverse classi con lo stesso nome in pacchetti diversi, quella riflessione usata nella risposta superiore potrebbe non riuscire a scegliere la classe corretta dall'oggetto. Quindi, se sai qual è il pacchetto.class dell'oggetto, allora è meglio accedere ai suoi valori del 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

È possibile utilizzare @JailBreak di Manifold per una riflessione Java diretta e sicura:

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

public class Foo {
    private String stuffIWant;
}

@JailBreaksblocca la foovariabile locale nel compilatore per l'accesso diretto a tutti i membri nella Foogerarchia di.

Allo stesso modo è possibile utilizzare il metodo di estensione jailbreak () per un utilizzo una tantum:

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

Attraverso il jailbreak()metodo è possibile accedere a qualsiasi membro nella Foogerarchia.

In entrambi i casi il compilatore risolve l'accesso al campo per te in modo sicuro, come se fosse un campo pubblico, mentre Manifold genera un codice di riflessione efficiente per te sotto il cofano.

Scopri di più sul collettore .


1

È abbastanza facile con lo strumento XrayInterface . Basta definire i getter / setter mancanti, ad es

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

e xray il tuo progetto povero progettato:

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

Internamente questo si basa sulla riflessione.


0

Prova ad aggirare il problema per il caso, il cui livello si desidera impostare / ottenere dati è una delle tue classi.

Basta creare un public setter(Field f, Object value)e public Object getter(Field f)per quello. Puoi anche fare un controllo sicuro delle tue funzioni membro. Ad esempio per il setter:

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

Certo, ora devi scoprire il java.lang.reflect.Fieldfor sStringprima di impostare il valore del campo.

Uso questa tecnica in un generico ResultSet-to-and-from-model-mapper.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.