it-swarm.com.de

Warum nicht statische innere Klassen statischen vorziehen?

Bei dieser Frage geht es darum, ob eine verschachtelte Klasse in Java als statische verschachtelte Klasse oder als innere verschachtelte Klasse erstellt werden soll). Ich habe hier und bei Stack Overflow gesucht, aber keine wirklichen Fragen gefunden in Bezug auf die Auswirkungen dieser Entscheidung auf das Design.

Die Fragen, die ich gefunden habe, beziehen sich auf den Unterschied zwischen statischen und inner verschachtelten Klassen, der mir klar ist. Ich habe jedoch noch keinen überzeugenden Grund gefunden, jemals eine statisch verschachtelte Klasse in Java - mit Ausnahme anonymer Klassen, die ich für diese Frage nicht berücksichtige) zu verwenden.

Hier ist mein Verständnis der Auswirkungen der Verwendung statisch verschachtelter Klassen:

  • Weniger Kopplung : Wir erhalten im Allgemeinen weniger Kopplung, da die Klasse nicht direkt auf die Attribute ihrer äußeren Klasse zugreifen kann. Weniger Kopplung bedeutet im Allgemeinen eine bessere Codequalität, einfacheres Testen, Refactoring usw.
  • Single Class : Der Klassenlader muss sich nicht jedes Mal um eine neue Klasse kümmern, wir erstellen ein Objekt der äußeren Klasse. Wir bekommen immer wieder neue Objekte für dieselbe Klasse.

Für eine innere Klasse finde ich im Allgemeinen, dass die Leute den Zugang zu den Attributen der äußeren Klasse als Profi betrachten. Ich möchte mich in dieser Hinsicht vom Design her unterscheiden, da dieser direkte Zugriff bedeutet, dass wir eine hohe Kopplung haben und wenn wir die verschachtelte Klasse jemals in ihre separate Klasse der obersten Ebene extrahieren wollen, können wir dies erst tun, nachdem wir uns im Wesentlichen gedreht haben es in eine statische verschachtelte Klasse.

Meine Frage lautet also: Bin ich falsch in der Annahme, dass der Attributzugriff, der für nicht statische innere Klassen verfügbar ist, zu einer hohen Kopplung und damit zu einer geringeren Codequalität führt und dass ich daraus schließe, dass (nicht anonyme) verschachtelte Klassen dies sollten im Allgemeinen statisch sein?

Oder mit anderen Worten: Gibt es einen überzeugenden Grund, warum man eine verschachtelte innere Klasse bevorzugen würde?

39
Frank

Joshua Bloch in Punkt 22 seines Buches "Effective Java Second Edition") gibt an, wann und warum eine verschachtelte Klasse verwendet werden soll. Nachfolgend einige Zitate:

Eine häufige Verwendung einer statischen Elementklasse ist die Verwendung einer öffentlichen Hilfsklasse, die nur in Verbindung mit ihrer äußeren Klasse nützlich ist. Stellen Sie sich beispielsweise eine Aufzählung vor, die die von einem Taschenrechner unterstützten Operationen beschreibt. Die Operation enum sollte eine öffentliche statische Memberklasse der Klasse Calculator sein. Clients von Calculator könnten dann mit Namen wie Calculator.Operation.PLUS Und Calculator.Operation.MINUS Auf Operationen verweisen.

Eine häufige Verwendung einer nicht statischen Elementklasse besteht darin, ein Adapter zu definieren, mit dem eine Instanz der äußeren Klasse als Instanz einer nicht verwandten Klasse angesehen werden kann. Beispielsweise verwenden Implementierungen der Schnittstelle Map normalerweise nicht statische Elementklassen, um ihre Sammlungsansichten zu implementieren, die von Map 's keySet zurückgegeben werden. Methoden entrySet und values. In ähnlicher Weise verwenden Implementierungen der Erfassungsschnittstellen wie Set und List normalerweise nicht statische Elementklassen, um ihre Iteratoren zu implementieren:

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

Wenn Sie eine Mitgliedsklasse deklarieren, für die kein Zugriff auf eine umschließende Instanz erforderlich ist, setzen Sie immer den Modifikator static in die Deklaration, sodass sie eher eine statische als eine nicht statische Mitgliedsklasse ist.

26
erakitin

Sie gehen zu Recht davon aus, dass der Attributzugriff, der nicht statischen inneren Klassen zur Verfügung steht, zu einer hohen Kopplung und damit zu einer geringeren Codequalität führt und (nicht anonyme und nicht lokale ) innere Klassen im Allgemeinen statisch sein sollten .

Die Entwurfsauswirkungen der Entscheidung, die innere Klasse nicht statisch zu machen, sind in Java Puzzlers , Puzzle 90 (Fettdruck unten) dargestellt Zitat ist meins):

Wenn Sie eine Mitgliedsklasse schreiben, fragen Sie sich: Benötigt diese Klasse wirklich eine umschließende Instanz? Wenn die Antwort nein ist, machen Sie es static. Innere Klassen sind manchmal nützlich, aber können leicht zu Komplikationen führen, die ein Programm schwer verständlich machen. Sie haben komplexe Wechselwirkungen mit Generika (Puzzle 89), Reflexion (Puzzle 80) und Vererbung (dieses Puzzle) . Wenn Sie Inner1 Als static deklarieren, verschwindet das Problem. Wenn Sie auch Inner2 Als static deklarieren, können Sie tatsächlich verstehen, was das Programm tut: ein wirklich schöner Bonus.

Zusammenfassend ist es selten angemessen, dass eine Klasse sowohl eine innere Klasse als auch eine Unterklasse einer anderen ist. Im Allgemeinen ist es selten angebracht, eine innere Klasse zu erweitern. Wenn Sie müssen, denken Sie lange und gründlich über die einschließende Instanz nach. Ziehen Sie außerdem static verschachtelte Klassen Nicht-static vor. Die meisten Mitgliedsklassen können und sollten als static deklariert werden.

Wenn Sie interessiert sind, finden Sie eine detailliertere Analyse von Puzzle 90 in diese Antwort bei Stack Overflow .


Es ist erwähnenswert, dass oben im Wesentlichen eine erweiterte Version der Anleitung in Java Klassen und Objekte Tutorial :

Verwenden Sie eine nicht statische verschachtelte Klasse (oder innere Klasse), wenn Sie Zugriff auf die nicht öffentlichen Felder und Methoden einer umschließenden Instanz benötigen. Verwenden Sie eine statisch verschachtelte Klasse, wenn Sie diesen Zugriff nicht benötigen.

Die Antwort auf die von Ihnen gestellte Frage mit anderen Worten pro Tutorial lautet also: Der einzige überzeugende Grund Nicht statische Verwendung ist der Zugriff auf eine umschließende Instanz Nicht öffentliche Felder und Methoden sind erforderlich.

Der Wortlaut des Tutorials ist etwas weit gefasst (dies kann der Grund sein, warum Java Puzzler versuchen, es zu stärken und einzugrenzen). Insbesondere war der direkte Zugriff auf umschließende Instanzfelder nie wirklich . -erforderlich meiner Erfahrung nach - in dem Sinne, dass alternative Methoden wie das Übergeben dieser als Konstruktor-/Methodenparameter immer einfacher zu debuggen und zu warten waren.


Insgesamt machten meine (ziemlich schmerzhaften) Begegnungen mit dem Debuggen innerer Klassen, die direkt auf Felder umschließender Instanzen zugreifen, den starken Eindruck, dass diese Praxis der Verwendung des globalen Zustands ähnelt, zusammen mit bekannten Übeln , die damit verbunden sind.

Natürlich macht Java es so, dass der Schaden eines solchen "quasi globalen" in der einschließenden Klasse enthalten ist, aber als ich eine bestimmte innere Klasse debuggen musste, fühlte es sich so an, als ob eine solche Pflasterung nicht funktioniert hätte. ' Es hilft nicht, Schmerzen zu lindern: Ich musste immer noch "fremde" Semantik und Details berücksichtigen, anstatt mich voll und ganz auf die Analyse eines bestimmten störenden Objekts zu konzentrieren.


Der Vollständigkeit halber gibt es kann Fälle sein wo die obigen Überlegungen nicht zutreffen. Zum Beispiel schlägt diese Funktion gemäß meiner Lesart von map.keySet javadocs eine enge Kopplung vor und macht infolgedessen Argumente gegen nicht statische Klassen ungültig:

Gibt eine Set Ansicht der in dieser Map enthaltenen Schlüssel zurück. Das Set wird von der Karte unterstützt, sodass Änderungen an der Karte im Set berücksichtigt werden und umgekehrt ...

Nicht, dass das oben Genannte das Verwalten, Testen und Debuggen des betroffenen Codes irgendwie erleichtern würde, aber es könnte zumindest erlauben, zu argumentieren, dass Komplikationen mit der beabsichtigten Funktionalität übereinstimmen/gerechtfertigt sind.

10
gnat

Einige Missverständnisse:

Weniger Kopplung: Wir erhalten im Allgemeinen weniger Kopplung, da die [statische innere] Klasse nicht direkt auf die Attribute ihrer äußeren Klasse zugreifen kann.

IIRC - Eigentlich kann es. (Wenn es sich um Objektfelder handelt, muss natürlich kein äußeres Objekt betrachtet werden. Wenn es jedoch ein solches Objekt von irgendwoher erhält, kann es direkt auf diese Felder zugreifen.).

Einzelne Klasse: Der Klassenlader muss sich nicht jedes Mal um eine neue Klasse kümmern, wir erstellen ein Objekt der äußeren Klasse. Wir bekommen immer wieder neue Objekte für dieselbe Klasse.

Ich bin mir nicht ganz sicher, was du hier meinst. Der Klassenlader ist insgesamt nur zweimal beteiligt, einmal für jede Klasse (wenn die Klasse geladen ist).


Wandern folgt:

In Javaland scheinen wir ein bisschen Angst zu haben, Klassen zu erstellen. Ich denke, es muss sich wie ein bedeutendes Konzept anfühlen. Es gehört in der Regel in eine eigene Datei. Es braucht erhebliche Kesselplatte. Es braucht Aufmerksamkeit für sein Design.

Im Vergleich zu anderen Sprachen - z. B. Scala oder vielleicht C++: Es gibt absolut kein Drama, das einen winzigen Halter (Klasse, Struktur, was auch immer) erzeugt, wenn zwei Datenbits zusammengehören. Flexible Zugriffsregeln helfen Ihnen, indem sie die Boilerplate reduzieren und den zugehörigen Code physisch näher halten. Dies verringert Ihre "Gedankenentfernung" zwischen den beiden Klassen.

Wir können Designbedenken über den direkten Zugang abwenden, indem wir die innere Klasse privat halten.

(... Wenn Sie gefragt hätten, warum eine nicht statische öffentliche innere Klasse?, Ist das eine sehr gute Frage - ich glaube, ich habe noch nie einen berechtigten Fall für diese gefunden.) .

Sie können sie jederzeit verwenden, um die Lesbarkeit des Codes zu verbessern und DRY Verstöße und Boilerplate) zu vermeiden. Ich habe kein fertiges Beispiel, da es meiner Erfahrung nach nicht allzu oft vorkommt, aber es tut es geschehen.


Statische innere Klassen sind übrigens immer noch ein guter Standardansatz . Nicht statisch hat ein zusätzliches verstecktes Feld erzeugt, durch das sich die innere Klasse auf die äußere bezieht.

1
Iain

Es kann verwendet werden, um ein Factory-Muster zu erstellen. Ein Factory-Muster wird häufig verwendet, wenn die erstellten Objekte zusätzliche Konstruktorparameter benötigen, um zu funktionieren, aber jedes Mal mühsam anzugeben sind:

Erwägen:

public class QueryFactory {
    @Inject private Database database;

    public Query create(String sql) {
        return new Query(database, sql);
    }
}

public class Query {
    private final Database database;
    private final String sql;

    public Query(Database database, String sql) {
        this.database = database;
        this.sql = sql;
    } 

    public List performQuery() {
        return database.query(sql);
    }
}

Benutzt als:

Query q = queryFactory.create("SELECT * FROM employees");

q.performQuery();

Dasselbe könnte mit einer nicht statischen inneren Klasse erreicht werden:

public class QueryFactory {
    @Inject private Database database;

    public class Query {
        private final String sql;

        public Query(String sql) {
            this.sql = sql;
        }

        public List performQuery() {
            return database.query(sql);
        }
    }
}

Benutzen als:

Query q = queryFactory.new Query("SELECT * FROM employees");

q.performQuery();

Fabriken profitieren von speziell benannten Methoden wie create, createFromSQL oder createFromTemplate. Dasselbe kann auch hier in Form mehrerer innerer Klassen geschehen:

public class QueryFactory {

    public class SQL { ... }
    public class Native { ... }
    public class Builder { ... }

}

Von der Fabrik zur konstruierten Klasse ist weniger Parameterübergabe erforderlich, aber die Lesbarkeit kann etwas leiden.

Vergleichen Sie:

Queries.Native q = queries.new Native("select * from employees");

vs.

Query q = queryFactory.createNative("select * from employees");
0
john16384