Warum sollte ich Reflektion verwenden?

29

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 Reflection verwenden, und was ist der Unterschied zwischen der Verwendung von Reflection und der Instanziierung von Objekten und dem herkömmlichen Aufrufen von Methoden?

Hamzah Khammash
quelle
10
Bitte recherchieren Sie vor dem Posten. StackExchange enthält viel Material (wie @Jalayn vermerkt) und das Web im Allgemeinen zum Thema Reflektion. Ich schlage vor, dass Sie zB das Java-Tutorial zu Reflection lesen und danach zurückkommen, wenn Sie weitere konkrete Fragen haben.
Péter Török
1
Es muss eine Million Dummköpfe geben.
DeadMG
3
Mehr als ein paar professionelle Programmierer würden "so selten wie möglich, vielleicht sogar nie" antworten.
Ross Patterson

Antworten:

38
  • Reflexion ist viel langsamer als nur das Aufrufen von Methoden mit ihrem Namen, da die Metadaten im Bytecode überprüft werden müssen, anstatt nur vorkompilierte Adressen und Konstanten zu verwenden.

  • Reflexion ist auch leistungsfähiger: Sie ist die Definition eines abrufen kann protectedoder finalMitglied, entfernen Sie den Schutz und manipulieren, als ob es wandelbar erklärt worden war! Offensichtlich untergräbt dies viele der Garantien, die die Sprache normalerweise für Ihre Programme bietet, 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 mutierbar, anstatt hinter den Rücken des Compilers 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ß, was die Mitglieder (oder sogar die Klassen) sein 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 ein triviales Spiegelbild: Es listet alle Methoden in Ihrer Klasse auf, nimmt an, dass alle aufgerufenen testXXXMethoden Testmethoden sind, und führt nur diese aus. Dies kann jetzt jedoch besser mit Anmerkungen geschehen, und tatsächlich wurde JUnit 4 größtenteils auf Anmerkungen umgestellt.

Kilian Foth
quelle
6
"Stärker" braucht Pflege. Sie benötigen keine Reflexion, um die Turing-Vollständigkeit zu erhalten, sodass keine Berechnung jemals eine Reflexion erfordert. Natürlich sagt Turing Complete nichts über andere Arten von Macht aus, wie E / A-Fähigkeiten und natürlich Reflexion.
Steve314
1
Reflexion ist nicht unbedingt "viel langsamer". Sie können Reflection einmal verwenden, um einen direkt aufrufenden Wrapper-Bytecode zu generieren.
SK-logic
2
Sie werden es wissen, wenn Sie es brauchen. Ich habe mich oft gefragt, warum (außerhalb der Sprachgeneration) man es brauchen würde. Dann, plötzlich, tat ich es ... Musste Eltern- / Kinderketten auf und ab laufen, um Daten einzusehen / zu stöbern, als ich Panels von anderen Entwicklern bekam, um sie in eines der Systeme zu stecken, die ich betreue.
Brian Knoblauch
@ SK-logic: Um Bytecode zu generieren, brauchen Sie überhaupt keine Reflektion (in der Tat enthält Reflektion keine API für die Bytecode-Manipulation!).
Joachim Sauer
1
@JoachimSauer natürlich, aber Sie benötigen eine Reflection-API, um diesen generierten Bytecode zu laden .
SK-logic
15

Ich war wie du einmal, ich wusste nicht viel über Reflexion - weiß es 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 aufwendig gewesen.

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

Mahmoud Hossam
quelle
4
Warum die Gegenstimme?
Mahmoud Hossam
1
Stimmabgabe für die Präambel
Dmitry
1
@MahmoudHossam Möglicherweise keine bewährte Methode, aber Ihre Antwort zeigt eine wichtige Taktik, die eingesetzt werden kann.
Ankush981
13

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

  1. Instanziieren beliebiger Klassen. Beispielsweise deklarieren Sie in einem Abhängigkeitsinjektionsframework wahrscheinlich, dass die Schnittstelle ThingDoer von der Klasse NetworkThingDoer implementiert wird. Das Framework findet dann den Konstruktor von NetworkThingDoer und instanziiert ihn.
  2. Marshalling und Unmarshalling in ein anderes Format. Beispiel: Mapping eines Objekts mit Gettern und Einstellungen, die der Bean-Konvention entsprechen, auf JSON und wieder zurück. Der Code kennt weder die Namen der Felder noch die Methoden, sondern untersucht nur die Klasse.
  3. Eine Klasse in eine Umleitungsebene einschließen (möglicherweise ist diese Liste nicht geladen, sondern nur ein Zeiger auf etwas, das weiß, wie man sie aus der Datenbank abruft) oder eine Klasse vollständig fälschen (jMock erstellt eine synthetische Klasse, die eine Schnittstelle implementiert zu Testzwecken).
Kevin Peterson
quelle
Dies ist die beste Erklärung für meine Überlegungen zu StackExchange. Die meisten Antworten wiederholen die Aussagen des Java-Trails (dh "Sie können auf alle diese Eigenschaften zugreifen", aber nicht, warum Sie dies tun würden), geben Beispiele für Überlegungen, auf die viel einfacher verzichtet werden kann, oder geben eine vage Antwort auf die Frage, wie Spring funktioniert benutzt es. Diese Antwort enthält tatsächlich drei gültige Beispiele, die von JVM NICHT ohne Weiteres bearbeitet werden können. Vielen Dank!
ndm13
3

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 Snippets, URLConnection c = nulldie durch ihr Vorhandensein 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, der verwendet wird, um Java-Programme unter OS X nativ erscheinen zu lassen, die auf anderen Plattformen nicht vorhanden sind.


quelle
2

Reflektion 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 ist. (Aber es gibt Kompromisse, daher ist es 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önnten 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 Boilerplate-Code, und jedes Mal, wenn Sie die Klasse ändern, müssen Sie den Code aktualisieren. Wirklich, Sie könnten 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 und die Namen, Typen und Werte seiner Eigenschaften. Hier kommt die Reflexion ins Spiel: Sie verschafft Ihnen Zugang zu diesen Informationen. Mit Java können Sie Typen mit den Methoden der ClassKlasse 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 Unit-Testfall beginnt

Vollständige Reflexion bedeutet jedoch nicht nur, vorhandenen Code (der an sich als "Introspektion" bezeichnet wird) zu betrachten, sondern auch Code zu ändern oder zu generieren. Hierfür gibt es in Java zwei wichtige Anwendungsfälle: Proxies 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 bewirkt:

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. (Umso mehr, wenn Sie weitere 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 übernimmt, und einen Logger
  • für jede Methode in der Schnittstelle
    • Die Implementierung protokolliert die Meldung "$ methodname called".
    • und ruft dann dieselbe Methode auf der inneren Schnittstelle auf, wobei alle Argumente übergeben werden

und die Eingabe dieses Algorithmus ist die Schnittstellendefinition.

Mit Reflection können Sie mit diesem Algorithmus eine neue Klasse definieren. Mit Java können Sie dies mit den Methoden der java.lang.reflect.ProxyKlasse tun , und es gibt Bibliotheken, die Ihnen noch mehr Leistung bieten.

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 ist schwerer zu debuggen. Insbesondere bei Code-generierenden Bibliotheken ist der ausgeführte Code möglicherweise nicht der von Ihnen geschriebene Code, aber 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 Laufzeit-Handles anstelle eines 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 löst zur Laufzeit Fehler aus.
Sebastian Redl
quelle
1

Mit Reflection können Teile Ihres Programms automatisch synchronisiert werden. Bisher mussten Sie Ihr Programm manuell aktualisieren, um die neuen Benutzeroberflächen zu verwenden.

DeadMG
quelle
5
Der Preis, den Sie in diesem Fall zahlen, besteht darin, dass Sie die Typprüfung durch den Compiler und die Refactor-Sicherheit in der IDE verlieren. Es ist ein Kompromiss, zu dem ich nicht bereit bin.
Barend