it-swarm-eu.dev

Warum sollte ich Reflexion verwenden?

Ich bin neu in Java. Während meines Studiums habe ich gelesen, dass Reflexion verwendet wird, um Klassen und Methoden aufzurufen und um zu wissen, welche Methoden implementiert sind oder nicht.

Wann sollte ich Reflexion verwenden, und was ist der Unterschied zwischen der Verwendung von Reflexion und der Instanziierung von Objekten und dem herkömmlichen Aufrufen von Methoden?

30
Hamzah khammash
  • Die Reflexion ist viel langsamer als das Aufrufen von Methoden anhand ihres Namens, da die Metadaten im Bytecode überprüft werden müssen, anstatt nur vorkompilierte Adressen und Konstanten zu verwenden.

  • Reflexion ist auch mächtiger: Sie können die Definition eines protected oder final Mitglieds abrufen, entfernen Sie den Schutz und manipulieren Sie es, als ob es für veränderlich erklärt worden wäre! Offensichtlich untergräbt dies viele der Garantien, die die Sprache normalerweise für Ihre Programme gibt, und kann sehr, sehr gefährlich sein.

Und das erklärt ziemlich genau, wann man es benutzt. Normalerweise nicht. Wenn Sie eine Methode aufrufen möchten, rufen Sie sie einfach auf. Wenn Sie ein Mitglied mutieren möchten, deklarieren Sie es einfach als veränderbar, anstatt hinter den Rücken der Kompilierung zu gehen.

Eine nützliche Anwendung der Reflexion in der Praxis ist das Schreiben eines Frameworks, das mit benutzerdefinierten Klassen zusammenarbeiten muss, wobei der Framework-Autor nicht weiß, wie die Mitglieder (oder sogar die Klassen) aussehen werden. Reflexion ermöglicht es ihnen, mit jeder Klasse umzugehen, ohne es vorher zu wissen. Ich glaube zum Beispiel nicht, dass es möglich wäre, eine komplexe aspektorientierte Bibliothek ohne Reflexion zu schreiben.

Als weiteres Beispiel verwendete JUnit eine triviale Reflexion: Es listet alle Methoden in Ihrer Klasse auf, nimmt an, dass alle mit testXXX bezeichneten Methoden Testmethoden sind, und führt nur diese aus. Aber dies kann jetzt besser mit Anmerkungen gemacht werden, und tatsächlich hat sich JUnit 4 stattdessen weitgehend auf Anmerkungen verlagert.

39
Kilian Foth

Ich war einmal wie du, ich wusste nicht viel über Reflexion - immer noch nicht - aber ich habe es einmal benutzt.

Ich hatte eine Klasse mit zwei inneren Klassen, und jede Klasse hatte viele Methoden.

Ich musste alle Methoden in der inneren Klasse aufrufen, und das manuelle Aufrufen wäre zu viel Arbeit gewesen.

Mit Reflection konnte ich alle diese Methoden in nur 2-3 Codezeilen anstelle der Anzahl der Methoden selbst aufrufen.

17
Mahmoud Hossam

Ich würde die Verwendung von Reflexion in drei Gruppen einteilen:

  1. Beliebige Klassen instanziieren. In einem Abhängigkeitsinjektionsframework deklarieren Sie beispielsweise wahrscheinlich, dass die Schnittstelle ThingDoer von der Klasse NetworkThingDoer implementiert wird. Das Framework würde dann den Konstruktor von NetworkThingDoer finden und instanziieren.
  2. Marshalling und Unmarshalling in ein anderes Format. Beispiel: Zuordnen eines Objekts mit Gettern und Einstellungen, die der Bean-Konvention folgen, zu JSON und wieder zurück. Der Code kennt die Namen der Felder oder Methoden nicht wirklich, sondern untersucht nur die Klasse.
  3. Umschließen einer Klasse in eine Umleitungsebene (möglicherweise wird diese Liste nicht geladen, sondern nur ein Zeiger auf etwas, das weiß, wie man sie aus der Datenbank abruft) oder Fälschen einer Klasse vollständig (jMock erstellt eine synthetische Klasse, die eine Schnittstelle implementiert zu Testzwecken).
15
Kevin Peterson

Reflection ermöglicht es einem Programm, mit Code zu arbeiten, der möglicherweise nicht vorhanden ist, und dies auf zuverlässige Weise.

"Normaler Code" hat Ausschnitte wie URLConnection c = null, die durch ihre bloße Anwesenheit dazu führen, dass der Klassenlader die URLConnection-Klasse als Teil des Ladens dieser Klasse lädt, eine ClassNotFound-Ausnahme auslöst und beendet.

Mit Reflection können Sie Klassen basierend auf ihren Namen in Zeichenfolgenform laden und auf verschiedene Eigenschaften testen (nützlich für mehrere Versionen außerhalb Ihres Steuerelements), bevor Sie tatsächliche Klassen starten, die von ihnen abhängen. Ein typisches Beispiel ist der OS X-spezifische Code, mit dem Java -Programme unter OS X nativ aussehen, die auf anderen Plattformen nicht vorhanden sind.

3
user1249

Reflexion bedeutet grundsätzlich, den Code Ihres Programms als Daten zu verwenden.

Daher ist die Verwendung von Reflection möglicherweise eine gute Idee, wenn der Code Ihres Programms eine nützliche Datenquelle darstellt. (Aber es gibt Kompromisse, daher ist dies möglicherweise nicht immer eine gute Idee.)

Stellen Sie sich zum Beispiel eine einfache Klasse vor:

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

und Sie möchten XML daraus generieren. Sie können Code schreiben, um das XML zu generieren:

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

Dies ist jedoch eine Menge Code, und jedes Mal, wenn Sie die Klasse ändern, müssen Sie den Code aktualisieren. Sie könnten wirklich beschreiben, wie dieser Code funktioniert

  • erstellen Sie ein XML-Element mit dem Namen der Klasse
  • für jede Eigenschaft der Klasse
    • erstellen Sie ein XML-Element mit dem Namen der Eigenschaft
    • fügen Sie den Wert der Eigenschaft in das XML-Element ein
    • fügen Sie das XML-Element zum Stammverzeichnis hinzu

Dies ist ein Algorithmus, und die Eingabe des Algorithmus ist die Klasse: Wir benötigen seinen Namen sowie die Namen, Typen und Werte seiner Eigenschaften. Hier kommt die Reflexion ins Spiel: Sie erhalten Zugriff auf diese Informationen. Java können Sie Typen mit den Methoden der Klasse Class untersuchen.

Einige weitere Anwendungsfälle:

  • definieren Sie URLs in einem Webserver basierend auf den Methodennamen einer Klasse und URL-Parameter basierend auf den Methodenargumenten
  • konvertieren Sie die Struktur einer Klasse in eine GraphQL-Typdefinition
  • rufen Sie jede Methode einer Klasse auf, deren Name mit "test" als Einheitentestfall beginnt

Vollständige Reflexion bedeutet jedoch, nicht nur vorhandenen Code zu betrachten (der an sich als "Selbstbeobachtung" bezeichnet wird), sondern auch Code zu ändern oder zu generieren. In Java hierfür) gibt es zwei wichtige Anwendungsfälle: Proxys und Mocks.

Angenommen, Sie haben eine Schnittstelle:

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

und Sie haben eine Implementierung, die etwas Interessantes macht:

public class PowerFroobnicator implements Froobnicator {
  // awesome implementations
}

Und tatsächlich haben Sie auch eine zweite Implementierung:

public class EnergySaverFroobnicator implements Froobnicator {
  // efficient implementations
}

Jetzt möchten Sie auch eine Protokollausgabe. Sie möchten einfach eine Protokollnachricht, wenn eine Methode aufgerufen wird. Sie könnten jeder Methode explizit eine Protokollausgabe hinzufügen, aber das wäre ärgerlich, und Sie müssten es zweimal tun. einmal für jede Implementierung. (Also noch mehr, wenn Sie mehr Implementierungen hinzufügen.)

Stattdessen können Sie einen Proxy schreiben:

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
}

Wieder gibt es jedoch ein sich wiederholendes Muster, das durch einen Algorithmus beschrieben werden kann:

  • ein Logger-Proxy ist eine Klasse, die eine Schnittstelle implementiert
  • es hat einen Konstruktor, der eine andere Implementierung der Schnittstelle benötigt, und einen Logger
  • für jede Methode in der Schnittstelle
    • die Implementierung protokolliert die Meldung "$ methodname aufgerufen".
    • und ruft dann dieselbe Methode auf der inneren Schnittstelle auf und übergibt alle Argumente

und die Eingabe dieses Algorithmus ist die Schnittstellendefinition.

Mit Reflection können Sie mit diesem Algorithmus eine neue Klasse definieren. Java ermöglicht es Ihnen, dies mit den Methoden des Java.lang.reflect.Proxy Klasse, und es gibt Bibliotheken, die Ihnen noch mehr Leistung geben.

Was sind die Nachteile der Reflexion?

  • Ihr Code wird schwerer zu verstehen. Sie sind eine Abstraktionsebene, die weiter von den konkreten Auswirkungen Ihres Codes entfernt ist.
  • Ihr Code wird schwieriger zu debuggen. Insbesondere bei Code-generierenden Bibliotheken ist der ausgeführte Code möglicherweise nicht der von Ihnen geschriebene Code, sondern der von Ihnen generierte Code, und der Debugger kann Ihnen diesen Code möglicherweise nicht anzeigen (oder Sie können Haltepunkte setzen).
  • Ihr Code wird langsamer. Das dynamische Lesen von Typinformationen und der Zugriff auf Felder über ihre Laufzeithandles anstelle des hartcodierten Zugriffs ist langsamer. Die dynamische Codegenerierung kann diesen Effekt abschwächen, was das Debuggen noch schwieriger macht.
  • Ihr Code wird möglicherweise anfälliger. Der dynamische Reflection-Zugriff wird vom Compiler nicht typgeprüft, sondern wirft zur Laufzeit Fehler aus.
3
Sebastian Redl

Reflection kann Teile Ihres Programms automatisch synchron halten, wobei Sie zuvor Ihr Programm manuell aktualisieren mussten, um die neuen Schnittstellen zu verwenden.

1
DeadMG