wake-up-neo.net

Verwendung von Initialisierern vs Konstruktoren in Java

Daher habe ich in letzter Zeit meine Java -Fähigkeiten aufgefrischt und ein paar Funktionen gefunden, die ich vorher nicht kannte. Statische und Instanzinitialisierer sind zwei solche Techniken.

Meine Frage ist, wann man einen Initialisierer benutzen würde, anstatt den Code in einen Konstruktor einzuschließen? Ich habe mir ein paar offensichtliche Möglichkeiten überlegt:

  • statische/Instanz-Initialisierer können verwendet werden, um den Wert von "endgültigen" statischen/Instanz-Variablen festzulegen, während dies ein Konstruktor nicht kann

  • mit statischen Initialisierern können Sie den Wert aller statischen Variablen in einer Klasse festlegen. Dies sollte effizienter sein, als wenn am Anfang eines jeden Konstruktors ein Codeblock "if (someStaticVar == null) // do stuff" steht

In beiden Fällen wird davon ausgegangen, dass der zum Festlegen dieser Variablen erforderliche Code komplexer ist als nur "var = value", da es sonst keinen Grund zu geben scheint, einen Initialisierer zu verwenden, anstatt nur den Wert beim Deklarieren der Variablen festzulegen.

Obwohl dies keine trivialen Vorteile sind (insbesondere die Möglichkeit, eine endgültige Variable festzulegen), scheint es doch eine recht begrenzte Anzahl von Situationen zu geben, in denen ein Initialisierer verwendet werden sollte.

Natürlich kann man einen Initialisierer für vieles verwenden, was in einem Konstruktor gemacht wird, aber ich sehe nicht wirklich den Grund dafür. Selbst wenn alle Konstruktoren für eine Klasse eine große Menge an Code gemeinsam haben, erscheint mir die Verwendung einer privaten Funktion initialize () sinnvoller als die Verwendung eines Initialisierers, da Sie dadurch nicht daran gehindert werden, diesen Code beim Schreiben eines neuen Codes auszuführen Konstrukteur.

Vermisse ich etwas? Gibt es eine Reihe anderer Situationen, in denen ein Initialisierer verwendet werden sollte? Oder ist es wirklich nur ein begrenztes Werkzeug, um in ganz bestimmten Situationen eingesetzt zu werden?

92
Inertiatic

Statische Initialisierer sind nützlich, da Cletus erwähnt wird, und ich verwende sie auf die gleiche Weise. Wenn Sie eine statische Variable haben, die beim Laden der Klasse initialisiert werden soll, ist ein statischer Initialisierer der richtige Weg, zumal Sie eine komplexe Initialisierung durchführen können und die statische Variable trotzdem final sein muss. . Dies ist ein großer Gewinn.

Ich finde "if (someStaticVar == null) // do stuff" chaotisch und fehleranfällig. Wenn es statisch initialisiert und als final deklariert wird, vermeiden Sie die Möglichkeit, dass es null ist.

Ich bin jedoch verwirrt, wenn Sie sagen:

statische/Instanz-Initialisierer können verwendet werden, um den Wert von "endgültigen" statischen/Instanz-Variablen festzulegen, während dies ein Konstruktor nicht kann

Ich nehme an, Sie sagen beide:

  • statische Initialisierer können verwendet werden, um den Wert "endgültiger" statischer Variablen festzulegen, während dies ein Konstruktor nicht kann
  • instanzinitialisierer können verwendet werden, um den Wert von "final" -Instanzvariablen festzulegen, während dies ein Konstruktor nicht kann

und Sie sind im ersten Punkt richtig, im zweiten falsch. Sie können zum Beispiel Folgendes tun:

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

Wenn Konstruktoren viel Code gemeinsam nutzen, ist es am besten, Konstruktoren mit den Standardwerten zu verketten. Dies macht ziemlich deutlich, was gerade getan wird:

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}
53
Eddie

Anonyme innere Klassen können keinen Konstruktor haben (da sie anonym sind), so dass sie für Initialisierer wie geschaffen sind.

52
Alex Martelli

Am häufigsten verwende ich statische Initialisierungsblöcke zum Einrichten endgültiger statischer Daten, insbesondere Sammlungen. Beispielsweise:

public class Deck {
  private final static List<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    list.add("Clubs");
    list.add("Spades");
    list.add("Hearts");
    list.add("Diamonds");
    SUITS = Collections.unmodifiableList(list);
  }

  ...
}

Dieses Beispiel kann nun mit einer einzelnen Codezeile erstellt werden:

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

die statische Version kann jedoch weitaus übersichtlicher sein, insbesondere wenn die zu initialisierenden Elemente nicht trivial sind.

Eine naive Implementierung erstellt möglicherweise auch keine nicht änderbare Liste, was ein potenzieller Fehler ist. Das Obige erstellt eine unveränderliche Datenstruktur, die Sie problemlos von öffentlichen Methoden usw. zurückgeben können.

26
cletus

Nur um einige bereits ausgezeichnete Punkte hier hinzuzufügen. Der statische Initialisierer ist threadsicher. Es wird ausgeführt, wenn die Klasse geladen wird, und ermöglicht somit eine einfachere Initialisierung statischer Daten als die Verwendung eines Konstruktors, bei dem Sie einen synchronisierten Block benötigen, um zu überprüfen, ob die statischen Daten initialisiert sind, und sie dann tatsächlich zu initialisieren.

public class MyClass {

    static private Properties propTable;

    static
    {
        try 
        {
            propTable.load(new FileInputStream("/data/user.prop"));
        } 
        catch (Exception e) 
        {
            propTable.put("user", System.getProperty("user"));
            propTable.put("password", System.getProperty("password"));
        }
    }

versus

public class MyClass 
{
    public MyClass()
    {
        synchronized (MyClass.class) 
        {
            if (propTable == null)
            {
                try 
                {
                    propTable.load(new FileInputStream("/data/user.prop"));
                } 
                catch (Exception e) 
                {
                    propTable.put("user", System.getProperty("user"));
                    propTable.put("password", System.getProperty("password"));
                }
            }
        }
    }

Vergessen Sie nicht, dass Sie jetzt auf Klassen- und nicht auf Instanzebene synchronisieren müssen. Dies verursacht Kosten für jede Instanz, die erstellt wird, anstatt einmaliger Kosten, wenn die Klasse geladen wird. Außerdem ist es hässlich ;-)

14
Robin

Ich habe einen ganzen Artikel gelesen und nach einer Antwort auf die Init-Reihenfolge von Initialisierern im Vergleich zu ihren Konstruktoren gesucht. Ich habe es nicht gefunden, also habe ich einen Code geschrieben, um mein Verständnis zu überprüfen. Ich dachte, ich würde diese kleine Demonstration als Kommentar hinzufügen. Um Ihr Verständnis zu testen, prüfen Sie, ob Sie die Antwort vorhersagen können, bevor Sie sie unten lesen.

/**
 * Demonstrate order of initialization in Java.
 * @author Daniel S. Wilkerson
 */
public class CtorOrder {
  public static void main(String[] args) {
    B a = new B();
  }
}

class A {
  A() {
    System.out.println("A ctor");
  }
}

class B extends A {

  int x = initX();

  int initX() {
    System.out.println("B initX");
    return 1;
  }

  B() {
    super();
    System.out.println("B ctor");
  }

}

Ausgabe:

Java CtorOrder
A ctor
B initX
B ctor
13
Daniel

Ein statischer Initialisierer entspricht einem Konstruktor im statischen Kontext. Sie werden das sicherlich öfter sehen als einen Instanzinitialisierer. Manchmal müssen Sie Code ausführen, um die statische Umgebung einzurichten.

Im Allgemeinen ist ein Instanzinitalisierer für anonyme innere Klassen am besten geeignet. Schauen Sie sich JMocks Kochbuch an, um eine innovative Möglichkeit zu finden, Code lesbarer zu machen.

Wenn Sie eine komplizierte Logik haben, die sich über Konstruktoren hinweg verketten lässt (z. B. wenn Sie Unterklassen bilden und dies nicht aufrufen können (), weil Sie super () aufrufen müssen), können Sie Doppelarbeit vermeiden, indem Sie die allgemeinen Aufgaben in der Instanz ausführen Initalizer. Instanzinitalisierer sind jedoch so selten, dass sie für viele eine überraschende Syntax darstellen. Daher vermeide ich sie und mache meine Klasse lieber konkret und nicht anonym, wenn ich das Konstruktorverhalten benötige.

JMock ist eine Ausnahme, denn so soll das Framework verwendet werden.

7
Yishai

Es gibt einen wichtigen Aspekt, den Sie bei Ihrer Auswahl berücksichtigen müssen:

Initialisierungsblöcke sind Member der Klasse/des Objekts, während Konstruktoren nicht. Dies ist wichtig, wenn Sie Erweiterung/Unterklasse berücksichtigen:

  1. Initialisierer werden vererbt von Unterklassen. (Allerdings kann beschattet werden)
    Dies bedeutet, dass grundsätzlich garantiert ist, dass Unterklassen wie von der Elternklasse beabsichtigt initialisiert werden.
  2. Konstruktoren werden aber nicht vererbt. (Sie rufen super() [d. H. Keine Parameter] nur implizit auf, oder Sie müssen einen bestimmten super(...) -Aufruf manuell ausführen.)
    Dies bedeutet, dass ein impliziter oder ausschließlicher Aufruf von super(...) die Unterklasse möglicherweise nicht wie von der übergeordneten Klasse beabsichtigt initialisiert.

Betrachten Sie dieses Beispiel eines Initialisierungsblocks:

class ParentWithInitializer {
    protected final String aFieldToInitialize;

    {
        aFieldToInitialize = "init";
        System.out.println("initializing in initializer block of: " 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithInitializer extends ParentWithInitializer{
    public static void main(String... args){
        System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
    }
}

ausgabe:
initializing in initializer block of: ChildOfParentWithInitializer init
-> Unabhängig davon, welche Konstruktoren die Unterklasse implementiert, wird das Feld initialisiert.

Betrachten Sie nun dieses Beispiel mit Konstruktoren:

class ParentWithConstructor {
    protected final String aFieldToInitialize;

    // different constructors initialize the value differently:
    ParentWithConstructor(){
        //init a null object
        aFieldToInitialize = null;
        System.out.println("Constructor of " 
            + this.getClass().getSimpleName() + " inits to null");
    }

    ParentWithConstructor(String... params) {
        //init all fields to intended values
        aFieldToInitialize = "intended init Value";
        System.out.println("initializing in parameterized constructor of:" 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithConstructor extends ParentWithConstructor{
    public static void main (String... args){
        System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
    }
}

ausgabe:
Constructor of ChildOfParentWithConstructor inits to null null
-> Dies initialisiert das Feld standardmäßig mit null, auch wenn dies möglicherweise nicht das gewünschte Ergebnis ist.

6
Vankog

Ich möchte auch einen Punkt zusammen mit all den oben genannten fabelhaften Antworten hinzufügen. Wenn wir einen Treiber in JDBC mit Class.forName ("") laden, wird die Klasse geladen und der statische Initialisierer der Driver-Klasse wird ausgelöst und der darin enthaltene Code registriert den Treiber im Driver Manager. Dies ist einer der Hauptgründe für die Verwendung des statischen Codeblocks.

4
kmrinal

Wie Sie bereits erwähnt haben, ist dies in vielen Fällen nicht sinnvoll. Wie bei jeder weniger verbreiteten Syntax möchten Sie wahrscheinlich vermeiden, dass die nächste Person, die sich Ihren Code ansieht, 30 Sekunden benötigt, um ihn aus den Tresoren zu ziehen.

Auf der anderen Seite ist es die einzige Möglichkeit, ein paar Dinge zu tun (ich denke, Sie haben diese ziemlich gut abgedeckt).

Statische Variablen selbst sollten sowieso vermieden werden - nicht immer, aber wenn Sie viele von ihnen verwenden, oder wenn Sie viele in einer Klasse verwenden, finden Sie möglicherweise unterschiedliche Ansätze, Ihr zukünftiges Selbst wird es Ihnen danken.

3
Bill K