Ist es eine schlechte Angewohnheit, Reflexion (über) zu verwenden?

16

Ist es eine gute Praxis, Reflektion zu verwenden, wenn die Anzahl der Kesselschild-Codes stark reduziert wird?

Grundsätzlich gibt es einen Kompromiss zwischen Leistung und Lesbarkeit auf der einen Seite und Abstraktion / Automatisierung / Reduzierung von Code auf der anderen Seite.

Bearbeiten: Hier ist ein Beispiel für eine empfohlene Verwendung der Reflexion .

Um ein Beispiel zu geben : Angenommen , es gibt eine eine abstrakte Klasse , Basedie 10 Felder hat und hat drei Unterklassen SubclassA, SubclassBund SubclassCjeweils mit 10 verschiedenen Bereichen; es sind alles einfache Bohnen. Das Problem ist, dass Sie zwei BaseTypreferenzen erhalten und sehen möchten, ob die entsprechenden Objekte vom gleichen (Unter-) Typ und gleich sind.

Als Lösungen gibt es die unformatierte Lösung, bei der Sie zuerst prüfen, ob die Typen gleich sind, und dann alle Felder prüfen, oder Sie können Reflection verwenden und dynamisch feststellen, ob sie vom gleichen Typ sind, und alle Methoden durchlaufen, die mit "get" (Konvention) beginnen über Konfiguration), rufen Sie sie für beide Objekte auf und rufen Sie gleich für die Ergebnisse auf.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}
m3th0dman
quelle
20
Überbeanspruchung ist eine schlechte Angewohnheit.
Tulains Córdova
1
@ user61852 Richtig, zu viel Freiheit führt zur Diktatur. Einige alte Griechen wussten bereits davon.
ott--
6
„Zu viel Wasser wäre schlecht für dich. Offensichtlich ist zu viel genau die Menge, die zu groß ist - das ist es, was es bedeutet! “- Stephen Fry
Jon Purdy
4
„Immer , wenn Sie finden sich Code der Form zu schreiben‚ wenn das Objekt vom Typ T1 ist, dann etwas tun, aber wenn es vom Typ T2 ist, dann etwas anderes tun‘ , schlagen Sie sich. Javapractices.com/topic/TopicAction.do?Id = 31
rm5248

Antworten:

25

Reflection wurde für einen bestimmten Zweck erstellt, um die Funktionalität einer Klasse zu ermitteln, die zum Kompilierungszeitpunkt unbekannt war, ähnlich wie die Funktionen dlopenund dlsymin C. Jede Verwendung außerhalb dieser Funktion sollte einer eingehenden Prüfung unterzogen werden.

Ist Ihnen jemals aufgefallen, dass die Java-Designer selbst auf dieses Problem gestoßen sind? Deshalb hat praktisch jede Klasse eine equalsMethode. Unterschiedliche Klassen haben unterschiedliche Definitionen von Gleichheit. Unter bestimmten Umständen kann ein abgeleitetes Objekt einem Basisobjekt entsprechen. Unter bestimmten Umständen könnte die Gleichstellung auf der Grundlage privater Felder ohne Getter bestimmt werden. Sie wissen es nicht.

Deshalb sollte jedes Objekt, das eine benutzerdefinierte Gleichheit wünscht, eine equalsMethode implementieren . Irgendwann möchten Sie die Objekte in eine Gruppe einfügen oder als Hash-Index verwenden, dann müssen Sie sie equalstrotzdem implementieren . Andere Sprachen machen es anders, aber Java verwendet equals. Sie sollten sich an die Konventionen Ihrer Sprache halten.

Außerdem ist der Code "boilerplate", wenn er in die richtige Klasse eingeordnet wird, ziemlich schwer zu verfälschen. Reflexion fügt zusätzliche Komplexität hinzu, was zusätzliche Möglichkeiten für Fehler bedeutet. In Ihrer Methode werden beispielsweise zwei Objekte als gleich betrachtet, wenn eines nullfür ein bestimmtes Feld zurückgegeben wird und das andere nicht. Was ist, wenn einer Ihrer Getter eines Ihrer Objekte ohne ein geeignetes zurückgibt equals? Du if (!object1.equals(object2))wirst scheitern. Außerdem ist es fehleranfällig, dass Reflexion nur selten verwendet wird, sodass Programmierer mit seinen Fallstricken nicht so vertraut sind.

Karl Bielefeldt
quelle
12

Übermäßiger Gebrauch von Reflexion hängt wahrscheinlich von der verwendeten Sprache ab. Hier verwenden Sie Java. In diesem Fall sollte Reflexion mit Vorsicht angewendet werden, da dies häufig nur eine Problemumgehung für schlechtes Design ist.

Wenn Sie also verschiedene Klassen vergleichen, ist dies ein perfektes Problem für das Überschreiben von Methoden . Beachten Sie, dass Instanzen von zwei verschiedenen Klassen niemals als gleich angesehen werden sollten. Sie können nur dann einen Vergleich auf Gleichheit durchführen, wenn Sie Instanzen derselben Klasse haben. Unter /programming/27581/overriding-equals-and-hashcode-in-java finden Sie ein Beispiel für die korrekte Implementierung des Gleichheitsvergleichs.

Sulthan
quelle
16
+1 - Einige von uns glauben, dass jede Verwendung von Reflektion eine rote Fahne ist, die auf ein schlechtes Design hinweist.
Ross Patterson
1
Höchstwahrscheinlich ist in diesem Fall eine Lösung auf der Grundlage von Gleichheit wünschenswert, aber als allgemeine Idee, was ist mit der Reflexionslösung falsch? Tatsächlich ist es sehr allgemein und die equals-Methoden müssen nicht in jeder Klasse und Unterklasse explizit geschrieben werden (obwohl sie leicht von einer guten IDE generiert werden können).
m3th0dman
2
@ RossPatterson Warum?
m3th0dman
2
@ m3th0dman Weil es zu Dingen wie Ihrer compare()Methode führt, bei denen angenommen wird, dass jede Methode, die mit "get" beginnt, ein Getter ist und als Teil einer Vergleichsoperation sicher und angemessen aufgerufen werden kann. Es ist eine Verletzung der beabsichtigten Schnittstellendefinition des Objekts, und obwohl dies zweckmäßig sein mag, ist es fast immer falsch.
Ross Patterson
4
@ m3th0dman Es verletzt die Kapselung - die Basisklasse muss auf Attribute in ihren Unterklassen zugreifen. Was ist, wenn sie privat (oder getter privat) sind? Was ist mit Polymorphismus? Wenn ich mich entscheide, eine andere Unterklasse hinzuzufügen und den Vergleich anders machen möchte? Nun, die Basisklasse macht das schon für mich und ich kann es nicht ändern. Was ist, wenn die Getter etwas faul laden? Möchte ich eine Vergleichsmethode, um das zu tun? Woher weiß ich überhaupt, dass eine Methode, die mit geteinem Getter beginnt, keine benutzerdefinierte Methode ist, die etwas zurückgibt?
Sulthan
1

Ich denke, Sie haben hier zwei Probleme.

  1. Wie viel dynamischen vs statischen Code sollte ich haben?
  2. Wie drücke ich eine benutzerdefinierte Version von Gleichheit aus?

Dynamischer vs statischer Code

Dies ist eine mehrjährige Frage und die Antwort ist sehr eigenwillig.

Einerseits ist Ihr Compiler sehr gut darin, alle Arten von schlechtem Code abzufangen. Dies geschieht durch verschiedene Formen der Analyse, wobei die Typanalyse eine übliche ist. Es ist bekannt, dass Sie Bananain Code, der a erwartet, kein Objekt verwenden können Cog. Dies wird durch einen Kompilierungsfehler angezeigt.

Dies ist jetzt nur möglich, wenn sowohl der akzeptierte Typ als auch der angegebene Typ aus dem Kontext abgeleitet werden können. Wie viel gefolgert werden kann und wie allgemein diese Schlussfolgerung ist, hängt weitgehend von der verwendeten Sprache ab. Java kann Typinformationen über Mechanismen wie Vererbung, Schnittstellen und Generika ableiten. Die Laufleistung variiert, einige andere Sprachen bieten weniger Mechanismen und einige mehr. Es läuft immer noch auf das hinaus, was der Compiler als wahr erkennen kann.

Andererseits kann Ihr Compiler die Form des Fremdcodes nicht vorhersagen, und manchmal kann ein allgemeiner Algorithmus über viele Typen ausgedrückt werden, die mit dem Typensystem der Sprache nicht einfach ausgedrückt werden können. In diesen Fällen kann der Compiler das Ergebnis nicht immer im Voraus kennen, und er kann möglicherweise nicht einmal wissen, welche Frage zu stellen ist. Reflection, Interfaces und die Object-Klasse sind Javas Methode, um diese Probleme zu lösen. Sie müssen die korrekten Überprüfungen und die korrekte Behandlung bereitstellen, aber es ist nicht ungesund, diese Art von Code zu haben.

Ob Ihr Code sehr spezifisch oder sehr allgemein sein soll, hängt von dem Problem ab, mit dem Sie sich befassen möchten. Wenn Sie es mit dem Typensystem leicht ausdrücken können, tun Sie dies. Lassen Sie den Compiler seine Stärken ausspielen und helfen Sie. Wenn das Typensystem möglicherweise nicht im Voraus weiß (Fremdcode) oder das Typensystem für eine allgemeine Implementierung Ihres Algorithmus schlecht geeignet ist, sind Reflexion (und andere dynamische Mittel) das richtige Werkzeug.

Denken Sie nur daran, dass es überraschend ist, aus dem Typensystem Ihrer Sprache herauszukommen. Stellen Sie sich vor, Sie gehen auf Ihren Freund zu und beginnen ein Gespräch auf Englisch. Lassen Sie plötzlich ein paar Wörter aus dem Spanischen, Französischen und Kantonesischen fallen, um Ihre Gedanken genau auszudrücken. Der Kontext wird Ihren Freunden viel erzählen, aber sie wissen möglicherweise auch nicht genau, wie sie mit diesen Worten umgehen sollen, die zu Missverständnissen aller Art führen. Ist es besser , mit diesen Missverständnissen umzugehen, als diese Ideen auf Englisch mit mehr Worten zu erklären?

Benutzerdefinierte Gleichstellung

Ich verstehe zwar, dass Java stark von der equalsMethode zum allgemeinen Vergleichen zweier Objekte abhängt, aber in einem bestimmten Kontext nicht immer geeignet ist.

Es gibt einen anderen Weg und es ist auch ein Java-Standard. Man nennt es einen Komparator .

Wie Sie Ihren Komparator implementieren, hängt davon ab, was und wie Sie vergleichen.

  • Es kann auf zwei beliebige Objekte angewendet werden, unabhängig von ihrer spezifischen equalsImplementierung.
  • Es könnte eine allgemeine (reflexionsbasierte) Vergleichsmethode für die Behandlung von zwei beliebigen Objekten implementieren.
  • Die Boilerplate-Vergleichsfunktionen können für häufig verglichene Objekttypen hinzugefügt werden, um die Typensicherheit und -optimierung zu gewährleisten.
Kain0_0
quelle
1

Ich bevorzuge es, reflektierende Programmierung so weit wie möglich zu vermeiden

  • erschwert die statische Überprüfung des Codes durch den Compiler
  • erschwert das Nachdenken über den Code
  • erschwert die Umgestaltung des Codes

Es ist auch viel weniger performant als einfache Methodenaufrufe; Früher war es um eine Größenordnung oder mehr langsamer.

Code statisch prüfen

Jeder reflektierende Code sucht mithilfe von Zeichenfolgen nach Klassen und Methoden. Im ursprünglichen Beispiel wird nach einer Methode gesucht, die mit "get" beginnt. Es wird Geter zurückgeben, aber auch andere Methoden, wie "gettysburgAddress ()". Die Regeln könnten im Code verschärft werden, aber der Punkt bleibt, dass es sich um eine Laufzeitprüfung handelt . Eine IDE und der Compiler können nicht helfen. Im Allgemeinen mag ich keinen "stringly typed" oder "primitive obsessed" Code.

Schwieriger zu überlegen

Reflektierender Code ist ausführlicher als einfache Methodenaufrufe. Mehr Code = mehr Fehler oder zumindest mehr Potenzial für Fehler, mehr Code zum Lesen, Testen usw. Weniger ist mehr.

Schwieriger zu refaktorieren

Weil der Code string / dynamisch basiert; Sobald die Reflektion vor Ort ist, können Sie den Code mit den Refactoring-Tools einer IDE nicht hundertprozentig umgestalten, da die IDE die reflektierenden Verwendungen nicht aufgreifen kann.

Vermeiden Sie grundsätzlich Reflexionen im allgemeinen Code, wenn dies möglich ist. Suchen Sie nach einem verbesserten Design.

David Kerr
quelle
0

Überbeanspruchung ist per definitionem schlecht, oder? Also lassen Sie uns die (Über) loswerden.

Würdest du den erstaunlich starken internen Gebrauch von Reflexion im Frühling als schlechte Angewohnheit bezeichnen?

Spring zähmt die Reflexion mithilfe von Anmerkungen - ebenso wie Hibernate (und wahrscheinlich Dutzende / Hunderte anderer Tools).

Folgen Sie diesen Mustern, wenn Sie sie in Ihrem eigenen Code verwenden. Verwenden Sie Anmerkungen, um sicherzustellen, dass die IDE Ihres Benutzers ihnen weiterhin helfen kann. (Auch wenn Sie der einzige "Benutzer" Ihres Codes sind, wird die unachtsame Verwendung der Reflexion Sie wahrscheinlich irgendwann wieder in den Hintern beißen.)

Ohne Rücksicht darauf, wie Ihr Code von den Entwicklern verwendet wird, ist jedoch die einfachste Verwendung von Reflection wahrscheinlich zu häufig.

Bill K
quelle
0

Ich denke, die meisten dieser Antworten verfehlen den Punkt.

  1. Ja, du solltest wohl schreiben equals()und hashcode(), wie von @KarlBielefeldt vermerkt.

  2. Aber für eine Klasse mit vielen Feldern kann dies ein mühsamer Boiler sein.

  3. Also, es hängt davon ab .

    • Wenn Sie nur selten Gleiche und Hash-Code benötigen , ist es pragmatisch und wahrscheinlich in Ordnung , eine Mehrzweck-Reflexionsberechnung zu verwenden. Zumindest als schneller und schmutziger erster Durchgang.
    • Wenn Sie jedoch eine Menge Gleicher benötigen, z. B. diese Objekte in HashTables abgelegt werden, sodass die Leistung ein Problem darstellt, sollten Sie den Code unbedingt ausschreiben. es wird viel schneller sein.
  4. Eine andere Möglichkeit: Wenn Ihre Klassen wirklich so viele Felder haben, dass das Schreiben eines Gleichheitszeichens mühsam ist, ziehen Sie in Betracht

    • Platzieren der Felder in einer Karte.
    • Sie können weiterhin benutzerdefinierte Getter und Setter schreiben
    • Verwenden Sie Map.equals()für Ihren Vergleich. (oder Map.hashcode())

zB ( Hinweis : Ignorieren von Null-Checks, sollte wahrscheinlich Enums anstelle von String-Schlüsseln verwenden, viel nicht gezeigt ...)

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
user949300
quelle