Perché dovrei usare la riflessione?

24

Sono nuovo di Java; attraverso i miei studi, ho letto che la riflessione è usata per invocare classi e metodi e per sapere quali metodi sono implementati o meno.

Quando dovrei usare la riflessione e qual è la differenza tra l'uso della riflessione e l'istanziazione di oggetti e metodi di chiamata nel modo tradizionale?

    
posta Hamzah khammash 08.12.2011 - 09:42
fonte

6 risposte

36
  • Reflection è molto più lento del semplice chiamare i metodi con il loro nome, perché deve ispezionare i metadati nel bytecode invece di usare solo gli indirizzi e le costanti precompilati.

  • Reflection è anche più potente: puoi recuperare la definizione di protected o final membro, rimuovere la protezione e manipolarla come se fosse stata dichiarata mutabile! Ovviamente questo sovverte molte delle garanzie che il linguaggio normalmente fa per i tuoi programmi e può essere molto, molto pericoloso.

E questo spiega praticamente quando usarlo. Normalmente no. Se vuoi chiamare un metodo, basta chiamarlo. Se vuoi mutare un membro, basta dichiararlo mutabile invece di andare dietro la schiena del compilatore.

Un utile uso del mondo reale della riflessione è quando si scrive un framework che deve interagire con classi definite dall'utente, dove l'autore del framework non sa cosa saranno i membri (o anche le classi). La riflessione consente loro di affrontare qualsiasi classe senza saperlo in anticipo. Per esempio, non penso che sarebbe possibile scrivere una biblioteca complessa orientata all'aspetto senza una riflessione.

Come altro esempio, JUnit usava un banale bit di riflessione: enumera tutti i metodi nella tua classe, presuppone che tutti quelli chiamati testXXX siano metodi di prova ed eseguono solo quelli. Ma ora questo può essere fatto meglio con le annotazioni, e in effetti JUnit 4 si è spostato in gran parte sulle annotazioni.

    
risposta data 08.12.2011 - 09:54
fonte
15

Ero come te una volta, non sapevo molto del riflesso - ancora non lo so - ma l'ho usato una volta.

Avevo una classe con due classi interne e ogni classe aveva molti metodi.

Avevo bisogno di invocare tutti i metodi nella classe interna, e invocarli manualmente sarebbe stato troppo lavoro.

Usando il reflection, potrei invocare tutti questi metodi in solo 2-3 righe di codice, invece del numero dei metodi stessi.

    
risposta data 14.12.2011 - 21:05
fonte
11

Vorrei raggruppare gli usi della riflessione in tre gruppi:

  1. Creazione di istanze di classi arbitrarie. Ad esempio, in un'infrastruttura per le dipendenze, probabilmente dichiari che l'interfaccia ThingDoer è implementata dalla classe NetworkThingDoer. Il framework troverebbe quindi il costruttore di NetworkThingDoer e lo istanziasse.
  2. Marshalling e unmarshalling in qualche altro formato. Ad esempio, mappare un oggetto con getter e impostazioni che seguono la convenzione bean su JSON e viceversa. Il codice in realtà non conosce i nomi dei campi o dei metodi, esamina solo la classe.
  3. Avvolgere una classe in un livello di reindirizzamento (forse l'elenco non è effettivamente caricato, ma solo un puntatore a qualcosa che sa come recuperarlo dal database) o fingere una classe completamente (jMock creerà una classe sintetica che implementa un'interfaccia per scopi di test).
risposta data 14.12.2011 - 19:03
fonte
3

Reflection consente a un programma di lavorare con codice che potrebbe non essere presente e farlo in modo affidabile.

Il "codice normale" ha snippet come URLConnection c = null che, per sua semplice presenza, fa sì che il caricatore di classi carichi la classe URLConnection come parte del caricamento di questa classe, generando un'eccezione ClassNotFound ed uscendo.

Reflection ti consente di caricare le classi in base al loro nome in forma di stringa e testarle per varie proprietà (utile per più versioni al di fuori del tuo controllo) prima di avviare classi reali che dipendono da esse. Un tipico esempio è il codice specifico OS X utilizzato per far apparire i programmi Java nativi sotto OS X, che non sono presenti su altre piattaforme.

    
risposta data 06.01.2012 - 13:10
fonte
1

Fondamentalmente, riflessione significa usare il codice del tuo programma come dati.

Pertanto, l'uso della riflessione potrebbe essere una buona idea quando il codice del programma è un'utile fonte di dati. (Ma ci sono dei compromessi, quindi potrebbe non essere sempre una buona idea.)

Ad esempio, considera una classe semplice:

public class Foo {
  public int value;
  public string anotherValue;
}

e vuoi generare XML da esso. Potresti scrivere codice per generare l'XML:

public XmlNode generateXml(Foo foo) {
  XmlElement root = new XmlElement("Foo");
  XmlElement valueElement = new XmlElement("value");
  valueElement.add(new XmlText(Integer.toString(foo.value)));
  root.add(valueElement);
  XmlElement anotherValueElement = new XmlElement("anotherValue");
  anotherValueElement.add(new XmlText(foo.anotherValue));
  root.add(anotherValueElement);
  return root;
}

Ma questo è un codice molto generico, e ogni volta che cambi la classe, devi aggiornare il codice. In realtà, potresti descrivere ciò che questo codice fa come

  • crea un elemento XML con il nome della classe
  • per ogni proprietà della classe
    • crea un elemento XML con il nome della proprietà
    • inserisce il valore della proprietà nell'elemento XML
    • aggiungi l'elemento XML alla radice

Questo è un algoritmo e l'input dell'algoritmo è la classe: abbiamo bisogno del suo nome e dei nomi, tipi e valori delle sue proprietà. È qui che entra in gioco la riflessione: ti dà accesso a queste informazioni. Java ti permette di ispezionare i tipi usando i metodi della classe Class .

Altri casi d'uso:

  • definisce gli URL in un server web in base ai nomi dei metodi di una classe e i parametri URL in base agli argomenti del metodo
  • converte la struttura di una classe in una definizione di tipo GraphQL
  • chiama ogni metodo di una classe il cui nome inizia con "test" come caso di test unitario

Tuttavia, la piena riflessione significa non solo guardare il codice esistente (che di per sé è noto come "introspezione"), ma anche modificare o generare codice. Ci sono due casi d'uso importanti in Java per questo: proxy e mock.

Supponiamo tu abbia un'interfaccia:

public interface Froobnicator {
  void froobnicateFruits(List<Fruit> fruits);
  void froobnicateFuel(Fuel fuel);
  // lots of other things to froobnicate
}

e hai un'implementazione che fa qualcosa di interessante:

public class PowerFroobnicator implements Froobnicator {
  // awesome implementations
}

E infatti hai anche una seconda implementazione:

public class EnergySaverFroobnicator implements Froobnicator {
  // efficient implementations
}

Ora vuoi anche qualche output di registro; vuoi semplicemente un messaggio di log ogni volta che viene chiamato un metodo. È possibile aggiungere in modo esplicito l'output del registro ad ogni metodo, ma ciò sarebbe fastidioso e dovresti farlo due volte; una volta per ogni implementazione. (Quindi ancora di più quando aggiungi ulteriori implementazioni.)

Invece, puoi scrivere un proxy:

public class LoggingFroobnicator implements Froobnicator {
  private Logger logger;
  private Froobnicator inner;

  // constructor that sets those two

  public void froobnicateFruits(List<Fruit> fruits) {
    logger.logDebug("froobnicateFruits called");
    inner.froobnicateFruits(fruits);
  }

  public void froobnicateFuel(Fuel fuel) {
    logger.logDebug("froobnicateFuel( called");
    inner.froobnicateFuel(fuel);
  }
  // lots of other things to froobnicate
}

Anche in questo caso, c'è un modello ripetitivo che può essere descritto da un algoritmo:

  • un proxy logger è una classe che implementa un'interfaccia
  • ha un costruttore che prende un'altra implementazione dell'interfaccia e un logger
  • per ogni metodo nell'interfaccia
    • l'implementazione registra un messaggio "$ methodname called"
    • e quindi chiama lo stesso metodo sull'interfaccia interna, passando lungo tutti gli argomenti

e l'input di questo algoritmo è la definizione dell'interfaccia.

Reflection ti permette di definire una nuova classe usando questo algoritmo. Java ti permette di farlo usando i metodi della classe java.lang.reflect.Proxy , e ci sono librerie che ti danno ancora più energia.

Quindi quali sono i lati negativi del riflesso?

  • Il tuo codice diventa più difficile da capire. Sei un livello di astrazione ulteriormente rimosso dagli effetti concreti del tuo codice.
  • Il codice diventa più difficile da eseguire il debug. Specialmente con le librerie generatrici di codice, il codice che è stato eseguito potrebbe non essere il codice che hai scritto, ma il codice che hai generato, e il debugger potrebbe non essere in grado di mostrarti quel codice (o di posizionare i breakpoint).
  • Il tuo codice diventa più lento. La lettura dinamica delle informazioni sul tipo e l'accesso ai campi tramite i relativi handle di runtime invece dell'accesso hard-coding è più lento. La generazione di codice dinamico può mitigare questo effetto, al costo di essere persino più difficile eseguire il debug.
  • Il tuo codice potrebbe diventare più fragile. L'accesso alla riflessione dinamica non è controllato dal compilatore, ma genera errori in fase di esecuzione.
risposta data 07.12.2018 - 10:21
fonte
0

Reflection può mantenere automaticamente sincronizzate parti del tuo programma, dove in precedenza avresti dovuto aggiornare manualmente il tuo programma per utilizzare le nuove interfacce.

    
risposta data 08.12.2011 - 09:54
fonte

Leggi altre domande sui tag