wake-up-neo.com

Soll ich Instanzvariablen bei der Deklaration oder im Konstruktor instanziieren?

Gibt es einen Vorteil für beide Ansätze?

Beispiel 1:

class A {
    B b = new B();
}

Beispiel 2

class A {
    B b;

    A() {
         b = new B();
    }
}
204
DonX
  • Es gibt keinen Unterschied - die Initialisierung der Instanzvariablen wird tatsächlich vom Compiler in die Konstruktoren gestellt.
  • Die erste Variante ist besser lesbar.
  • Sie können mit der ersten Variante keine Ausnahmebehandlung durchführen.
  • Es gibt zusätzlich den Initialisierungsblock, der ebenfalls vom Compiler in den Konstruktoren abgelegt wird:

    {
        a = new A();
    }
    

Überprüfen Sie Erklärung und Rat von Sun

Von dieses Tutorial :

Felddeklarationen sind jedoch nicht Teil einer Methode und können daher nicht wie Anweisungen ausgeführt werden. Stattdessen generiert der Compiler Java automatisch Instanzfeld-Initialisierungscode und fügt ihn in den Konstruktor oder die Konstruktoren der Klasse ein. Der Initialisierungscode wird in der Reihenfolge in einen Konstruktor eingefügt, in der er im Quellcode angezeigt wird. Dies bedeutet, dass ein Feldinitialisierer die Anfangswerte der zuvor deklarierten Felder verwenden kann.

Darüber hinaus möchten Sie möglicherweise Ihr Feld verzögert initialisieren . In Fällen, in denen das Initialisieren eines Feldes eine kostspielige Operation ist, können Sie es initialisieren, sobald es benötigt wird:

ExpensiveObject o;

public ExpensiveObject getExpensiveObject() {
    if (o == null) {
        o = new ExpensiveObject();
    }
    return o;
}

Und letztendlich (wie von Bill ausgeführt) ist es aus Gründen des Abhängigkeitsmanagements besser, den Operator new an einer beliebigen Stelle innerhalb von zu vermeiden deine Klasse. Stattdessen ist die Verwendung von Abhängigkeitsinjektion vorzuziehen - d. H., Dass eine andere Person (eine andere Klasse/ein anderes Framework) die Abhängigkeiten in Ihrer Klasse instanziiert und injiziert.

256
Bozho

Eine andere Option wäre die Verwendung von Dependency Injection .

class A{
   B b;

   A(B b) {
      this.b = b;
   }
}

Dadurch wird die Verantwortung für das Erstellen des B-Objekts aus dem Konstruktor von A entfernt. Dadurch wird Ihr Code auf lange Sicht testfähiger und einfacher zu warten. Die Idee ist, die Kopplung zwischen den beiden Klassen A und B zu reduzieren. Dies hat den Vorteil, dass Sie jetzt jedes Objekt übergeben können, das B erweitert (oder B implementiert, wenn es sich um eine Schnittstelle handelt) an den Konstruktor von A. Ein Nachteil besteht darin, dass Sie die Kapselung des B-Objekts aufgeben, sodass es dem Aufrufer des A-Konstruktors ausgesetzt ist. Sie müssen sich überlegen, ob die Vorteile diesen Kompromiss wert sind, in vielen Fällen aber auch.

35
Bill the Lizard

Ich wurde heute auf interessante Weise verbrannt:

class MyClass extends FooClass {
    String a = null;

    public MyClass() {
        super();     // Superclass calls init();
    }

    @Override
    protected void init() {
        super.init();
        if (something)
            a = getStringYadaYada();
    }
}

Sehen Sie den Fehler? Es stellt sich heraus, dass der a = null-Initialisierer after aufgerufen wird, und der Superklassenkonstruktor wird aufgerufen. Da der Superklassenkonstruktor init () aufruft, ist die Initialisierung von a gefolgt von von der a = null-Initialisierung.

16
Edward Falk

meine persönliche "Regel" (fast nie gebrochen) ist:

  • deklarieren Sie alle Variablen am Anfang eines Blocks
  • machen Sie alle Variablen final, es sei denn, sie können nicht
  • deklarieren Sie eine Variable pro Zeile
  • initialisieren Sie niemals eine Variable, in der deklariert wurde
  • initialisieren Sie etwas nur in einem Konstruktor, wenn Daten vom Konstruktor für die Initialisierung benötigt werden

Ich hätte also Code wie:

public class X
{
    public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
    private static final int A;
    private final int b;
    private int c;

    static 
    { 
        A = 42; 
    }

    {
        b = 7;
    }

    public X(final int val)
    {
        c = val;
    }

    public void foo(final boolean f)
    {
        final int d;
        final int e;

        d = 7;

        // I will eat my own eyes before using ?: - personal taste.
        if(f)
        {
            e = 1;
        }
        else
        {
            e = 2;
        }
    }
}

Auf diese Weise bin ich immer zu 100% sicher, wo nach Variablendeklarationen (zu Beginn eines Blocks) und deren Zuweisungen (sobald es nach der Deklaration sinnvoll ist) gesucht werden muss. Dies ist möglicherweise auch effizienter, da Sie niemals eine Variable mit einem Wert initialisieren, der nicht verwendet wird (z. B. "declare" und "init vars") und dann eine Ausnahme auslösen, bevor die Hälfte dieser Variablen einen Wert benötigt. Sie führen auch keine sinnlose Initialisierung durch (wie int i = 0; und dann, bevor "i" verwendet wird, gilt i = 5;).

Ich schätze die Konsistenz sehr, also folge ich dieser Regel, und es macht es viel einfacher, mit dem Code zu arbeiten, da Sie nicht suchen müssen, um Dinge zu finden. 

Ihre Laufleistung kann variieren.

14
TofuBeer

Beispiel 2 ist weniger flexibel. Wenn Sie einen anderen Konstruktor hinzufügen, müssen Sie daran denken, auch das Feld in diesem Konstruktor zu instanziieren. Sie können das Feld einfach direkt instanziieren oder das Lazy Loading irgendwo in einem Getter einführen.

Wenn für die Instantiierung mehr als nur ein einfacher new erforderlich ist, verwenden Sie einen Initialisierungsblock. Dies wird egaldes verwendeten Konstruktors ausgeführt. Z.B.

public class A {
    private Properties properties;

    {
        try {
            properties = new Properties();
            properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
        } catch (IOException e) {
            throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException.
        }
    }

    // ...

}
7
BalusC

Die Verwendung von Abhängigkeitsinjektion oder Lazy-Initialisierung ist immer vorzuziehen, wie bereits in anderen Antworten ausführlich erläutert.

Wenn Sie diese Muster nicht verwenden möchten oder können, und bei primitiven Datentypen, gibt es drei überzeugende Gründe, warum ich die Klassenattribute außerhalb des Konstruktors initialisieren sollte:

  1. vermiedene Wiederholung = Wenn Sie mehr als einen Konstruktor haben oder wenn Sie mehr hinzufügen müssen, müssen Sie die Initialisierung in allen Konstruktorkörpern nicht immer wiederholen.
  2. verbesserte Lesbarkeit = Sie können leicht erkennen, welche Variablen von außerhalb der Klasse initialisiert werden müssen.
  3. reduzierte Codezeilen = Bei jeder bei der Deklaration vorgenommenen Initialisierung wird im Konstruktor eine Zeile weniger angezeigt.
4
Marco Lackovic

Ich nehme an, es ist fast nur eine Frage des Geschmacks, solange die Initialisierung einfach ist und keine Logik erfordert. 

Der Konstruktor-Ansatz ist etwas anfälliger, wenn Sie keinen Initialisierungsblock verwenden. Wenn Sie später einen zweiten Konstruktor hinzufügen und vergessen, b dort zu initialisieren, wird nur bei Verwendung des letzten Konstruktors ein Null-B angezeigt.

Unter http://Java.Sun.com/docs/books/tutorial/Java/javaOO/initial.html finden Sie weitere Informationen zur Initialisierung in Java (und Erklärungen zu Initialisierungsblöcken und anderen nicht bekannten Initialisierungsfunktionen). .

4
Vinko Vrsalovic

Beide Methoden sind akzeptabel. Beachten Sie, dass b=new B() im letzteren Fall möglicherweise nicht initialisiert wird, wenn ein anderer Konstruktor vorhanden ist. Stellen Sie sich den Initialisierungscode außerhalb des Konstruktors als einen allgemeinen Konstruktor vor, und der Code wird ausgeführt. 

1
Chandra Patni

Ich denke, Beispiel 2 ist vorzuziehen. Ich denke, es ist am besten, wenn Sie außerhalb des Konstruktors deklarieren und im Konstruktor initialisieren.

1
jkeesh

Es gibt noch einen weiteren Grund, außerhalb des Konstruktors zu initialisieren, den noch niemand erwähnt hat (sehr spezifisch muss ich sagen). Wenn Sie UML-Werkzeuge verwenden, um Klassendiagramme aus dem Code zu generieren (Reverse Engineering), werden die meisten Werkzeuge, von denen ich glaube, dass sie die Initialisierung von Beispiel 1 notieren, in ein Diagramm übertragen (wenn Sie es vorziehen, die Anfangswerte anzuzeigen, wie z Ich mache). Diese Anfangswerte werden nicht aus Beispiel 2 übernommen. Dies ist wiederum ein sehr spezifischer Grund - wenn Sie mit UML-Werkzeugen arbeiten, aber sobald ich das gelernt habe, versuche ich, alle meine Standardwerte außerhalb des Konstruktors zu nehmen, sofern dies nicht der Fall war Wie bereits erwähnt, gibt es ein Problem mit möglichen Ausnahmen oder komplizierten Logik.

0
tulu

Die zweite Option ist vorzuziehen, da unterschiedliche Klassen in Klassen für die Instantiierung von Klassen und die Verkettung von Prozessoren verwendet werden. Z.B.

class A {
    int b;

    // secondary ctor
    A(String b) {
         this(Integer.valueOf(b));
    }

    // primary ctor
    A(int b) {
         this.b = b;
    }
}

Die zweite Möglichkeit ist also flexibler.

0
Andriy Kryvtsun

Die Deklaration erfolgt vor dem Bau! Wenn also die Variable (b in diesem Fall) an beiden Stellen initialisiert wurde, ist die Initialisierung des Konstruktors die endgültige Initialisierung. 

0
Imad S

Folgendes habe ich in den Antworten nicht gesehen:

Ein möglicher Vorteil der Initialisierung zum Zeitpunkt der Deklaration könnte bei IDEs von heute sein, bei denen Sie sehr leicht zur Deklaration einer Variablen (meistens Ctrl-<hover_over_the_variable>-<left_mouse_click>) von überall in Ihrem Code springen können. Sie sehen dann sofort den Wert dieser Variablen. Ansonsten müssen Sie nach dem Ort suchen, an dem die Initialisierung durchgeführt wird (meistens: Konstruktor).

Dieser Vorteil ist natürlich sekundär gegenüber allen anderen logischen Gründen, aber für manche Menschen könnte dieses "Feature" wichtiger sein. 

0
GeertVc

Die zweite ist ein Beispiel für eine langsame Initialisierung. Erstens ist es eine einfachere Initialisierung, sie ist im Wesentlichen gleich.

0
fastcodejava