Was ist der Unterschied zwischen einem statischen und einem nicht statischen Initialisierungscodeblock?

357

Meine Frage bezieht sich auf eine bestimmte Verwendung des statischen Schlüsselworts. Es ist möglich, ein staticSchlüsselwort zu verwenden, um einen Codeblock innerhalb einer Klasse abzudecken, die keiner Funktion angehört. Zum Beispiel wird folgender Code kompiliert:

public class Test {
    private static final int a;    
    static {
        a = 5;
        doSomething(a);
    }
    private static int doSomething(int x) {
        return (x+5);
    }
}

Wenn Sie das staticSchlüsselwort entfernen, wird es beschwert, weil die Variable aist final. Es ist jedoch möglich, sowohl Schlüsselwörter als auch Schlüsselwörter zu entfernen finalund statickompilieren zu lassen.

Es ist in vielerlei Hinsicht verwirrend für mich. Wie soll ich einen Codeabschnitt haben, der zu keiner Methode gehört? Wie ist es möglich, es aufzurufen? Was ist im Allgemeinen der Zweck dieser Verwendung? Oder besser, wo finde ich Dokumentation dazu?

Szere Dyeri
quelle

Antworten:

403

Der Codeblock mit dem statischen Modifikator kennzeichnet einen Klasseninitialisierer . ohne den statischen Modifikator der Codeblock ist eine Instanz Initialisierer.

Klasseninitialisierer werden in der Reihenfolge ausgeführt, in der sie definiert sind (von oben nach unten, genau wie einfache Variableninitialisierer), wenn die Klasse geladen wird (tatsächlich, wenn sie aufgelöst ist, aber das ist eine technische Angelegenheit).

Instanzinitialisierer werden in der Reihenfolge ausgeführt, die bei der Instanziierung der Klasse unmittelbar vor der Ausführung des Konstruktorcodes unmittelbar nach dem Aufruf des Superkonstruktors definiert wurde.

Wenn Sie entfernen staticaus int a, wird es eine Instanzvariable, die Sie Initialisierungsblocks für den Zugriff aus dem statischen nicht in der Lage sind. Dies kann nicht mit dem Fehler "Nicht statische Variable a kann nicht aus einem statischen Kontext referenziert werden" kompiliert werden.

Wenn Sie auch staticaus dem Initialisierungsblock entfernen , wird dieser zu einem Instanzinitialisierer und wird daher int abei der Erstellung initialisiert.

Lawrence Dol
quelle
Der statische Initialisierer wird tatsächlich später aufgerufen, wenn die Klasse initialisiert wird, nachdem sie geladen und verknüpft wurde. Dies geschieht, wenn Sie ein Objekt einer Klasse instanziieren oder auf eine statische Variable oder Methode für die Klasse zugreifen. In der Tat, wenn Sie eine Klasse mit einem statischen Initialisierer und einer Methode haben public static void staticMethod(){}, wenn Sie ausführen TestStatic.class.getMethod("staticMethod");. Der statische Initialisierer wird nicht aufgerufen. Weitere Informationen hier docs.oracle.com/javase/specs/jvms/se10/html/…
Totò
@ Totò: Ja, das ist es, was die Auflösung der Klasse beinhaltet (zumindest haben sie es früher als Link + Init als "Auflösung" bezeichnet, als). Ich bin nicht überrascht, dass Sie Reflexion verwenden können, um Dinge über eine Klasse zu entdecken, ohne dass sie sich auflöst.
Lawrence Dol
166

Uff! Was ist ein statischer Initialisierer?

Der statische Initialisierer ist ein static {}Codeblock innerhalb der Java-Klasse und wird nur einmal ausgeführt, bevor der Konstruktor oder die Hauptmethode aufgerufen wird.

OK! Erzähl mir mehr...

  • ist ein Codeblock static { ... }in jeder Java-Klasse. und von der virtuellen Maschine ausgeführt, wenn die Klasse aufgerufen wird.
  • Es returnwerden keine Anweisungen unterstützt.
  • Es werden keine Argumente unterstützt.
  • Nein thisoder superwerden unterstützt.

Hmm wo kann ich es verwenden?

Kann überall verwendet werden, wo du dich gut fühlst :) so einfach. Aber ich sehe, dass es die meiste Zeit verwendet wird, wenn Datenbankverbindungen, API-Init, Protokollierung usw. ausgeführt werden.

Bellen Sie nicht nur! wo ist beispiel

package com.example.learnjava;

import java.util.ArrayList;

public class Fruit {

    static {
        System.out.println("Inside Static Initializer.");

        // fruits array
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Pear");

        // print fruits
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        System.out.println("End Static Initializer.\n");
    }

    public static void main(String[] args) {
        System.out.println("Inside Main Method.");
    }
}

Ausgabe???

Inside Static Initializer.

Apfel

Orange

Birne

Static Initializer beenden.

Innerhalb der Hauptmethode.

Hoffe das hilft!

Madan Sapkota
quelle
Danke Madan! Kann statischer Block anstelle afterPropertiesSet()von verwendet werden InitializingBean?
Alexander Suraphel
3
Ja, du kannst! Der statische Initialisierer wird aufgerufen, wenn die Klasse von der JVM geladen wird. Es ist also die erste Phase, in der Code ausgeführt wird. Wenn Sie auch einen Konstruktor haben, wäre die Reihenfolge: statischer Initialisierer, Konstruktor, afterPropertiesSet
Martin Baumgartner
57

Der staticBlock ist ein "statischer Initialisierer".

Es wird automatisch aufgerufen, wenn die Klasse geladen wird, und es gibt keine andere Möglichkeit, sie aufzurufen (nicht einmal über Reflection).

Ich persönlich habe es nur beim Schreiben von JNI-Code verwendet:

class JNIGlue {
    static {
        System.loadLibrary("foo");
    }
}
Alnitak
quelle
6
Nein, keine explizite Möglichkeit, es aufzurufen. Der Klasseninitialisierer wird niemals durch eine MethodInstanz dargestellt, sondern nur von der virtuellen Java-Maschine.
Rafael Winterhalter
46

Dies ist direkt von http://www.programcreek.com/2011/10/java-class-instance-initializers/

1. Ausführungsreihenfolge

Schauen Sie sich die folgende Klasse an. Wissen Sie, welche zuerst ausgeführt wird?

public class Foo {

    //instance variable initializer
    String s = "abc";

    //constructor
    public Foo() {
        System.out.println("constructor called");
    }

    //static initializer
    static {
        System.out.println("static initializer called");
    }

    //instance initializer
    {
        System.out.println("instance initializer called");
    }

    public static void main(String[] args) {
        new Foo();
        new Foo();
    }
}

Ausgabe:

statischer Initialisierer aufgerufen

Instanzinitialisierer aufgerufen

Konstruktor aufgerufen

Instanzinitialisierer aufgerufen

Konstruktor aufgerufen

2. Wie funktioniert der Java-Instanzinitialisierer?

Der obige Instanzinitialisierer enthält eine println-Anweisung. Um zu verstehen, wie es funktioniert, können wir es als variable Zuweisungsanweisung behandeln, z b = 0. Dies kann das Verständnis offensichtlicher machen.

Anstatt

int b = 0, du könntest schreiben

int b;
b = 0;

Daher sind Instanzinitialisierer und Instanzvariableninitialisierer ziemlich gleich.

3. Wann sind Instanzinitialisierer nützlich?

Die Verwendung von Instanzinitialisierern ist selten, kann jedoch eine nützliche Alternative zu Instanzvariableninitialisierern sein, wenn:

  1. Der Initialisierungscode muss Ausnahmen behandeln
  2. Führen Sie Berechnungen durch, die mit einem Instanzvariableninitialisierer nicht ausgedrückt werden können.

Natürlich könnte ein solcher Code in Konstruktoren geschrieben werden. Wenn eine Klasse jedoch mehrere Konstruktoren hätte, müssten Sie den Code in jedem Konstruktor wiederholen.

Mit einem Instanzinitialisierer können Sie den Code nur einmal schreiben. Er wird ausgeführt, unabhängig davon, mit welchem ​​Konstruktor das Objekt erstellt wird. (Ich denke, dies ist nur ein Konzept und es wird nicht oft verwendet.)

Ein weiterer Fall, in dem Initialisierer nützlich sind, sind anonyme innere Klassen, die überhaupt keine Konstruktoren deklarieren können. (Ist dies ein guter Ort, um eine Protokollierungsfunktion zu platzieren?)

Danke an Derhein.

Beachten Sie auch, dass anonyme Klassen, die Schnittstellen [1] implementieren, keine Konstruktoren haben. Daher werden Instanzinitialisierer benötigt, um alle Arten von Ausdrücken zur Konstruktionszeit auszuführen.

Alexei Fando
quelle
12

"final" garantiert, dass eine Variable vor dem Ende des Objektinitialisierungscodes initialisiert werden muss. Ebenso garantiert "static final", dass eine Variable bis zum Ende des Klasseninitialisierungscodes initialisiert wird. Wenn Sie das "Statische" aus Ihrem Initialisierungscode weglassen, wird es in einen Objektinitialisierungscode umgewandelt. Somit erfüllt Ihre Variable ihre Garantien nicht mehr.

DJClayworth
quelle
8

Sie werden keinen Code in einen statischen Block schreiben, der irgendwo in Ihrem Programm aufgerufen werden muss. Wenn der Zweck des Codes aufgerufen werden soll, müssen Sie ihn in eine Methode einfügen.

Sie können statische Initialisierungsblöcke schreiben, um statische Variablen beim Laden der Klasse zu initialisieren. Dieser Code kann jedoch komplexer sein.

Ein statischer Initialisierungsblock sieht aus wie eine Methode ohne Namen, ohne Argumente und ohne Rückgabetyp. Da Sie es nie nennen, braucht es keinen Namen. Es wird nur aufgerufen, wenn die virtuelle Maschine die Klasse lädt.

Vincent Ramdhanie
quelle
6

Wenn ein Entwickler einen Initialisiererblock verwendet, kopiert der Java-Compiler den Initialisierer in jeden Konstruktor der aktuellen Klasse.

Beispiel:

den folgenden Code:

class MyClass {

    private int myField = 3;
    {
        myField = myField + 2;
        //myField is worth 5 for all instance
    }

    public MyClass() {
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

ist äquivalent zu:

class MyClass {

    private int myField = 3;

    public MyClass() {
        myField = myField + 2;
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        myField = myField + 2;
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

Ich hoffe, mein Beispiel wird von Entwicklern verstanden.

Cardman
quelle
4

Der statische Codeblock kann zum Instanziieren oder Initialisieren von Klassenvariablen verwendet werden (im Gegensatz zu Objektvariablen). Das Deklarieren von "a" static bedeutet also, dass nur eines von allen Testobjekten gemeinsam genutzt wird, und der statische Codeblock initialisiert "a" nur einmal, wenn die Testklasse zum ersten Mal geladen wird, unabhängig davon, wie viele Testobjekte erstellt werden.

Paul Tomblin
quelle
Wenn ich im Nachhinein keine Instanz des Objekts erstelle, sondern stattdessen eine öffentliche statische Funktion aufrufe. Bedeutet dies, dass die Ausführung dieses Blocks vor diesem öffentlichen Funktionsaufruf garantiert ist? Vielen Dank.
Szere Dyeri
Wenn Sie eine öffentliche statische Funktion der Klasse aufrufen, muss die Klasse zuerst geladen werden. Ja, der statische Initialisierer wird zuerst ausgeführt.
Paul Tomblin
Es sei denn, es war die Klasseninitialisierung, die (indirekt) den Code aufrief, der versucht, ihn zu verwenden. IFYSWIM. Zirkuläre Abhängigkeiten und so weiter.
Tom Hawtin - Tackline
1
@Tom ist richtig - es ist möglich, etwas zu schreiben, bei dem ein statischer Initialisierer eine statische Methode aufruft, bevor ein anderer statischer Initialisierer aufgerufen wird, aber meine Gedanken schrecken bei dem Gedanken zurück, sodass ich nie darüber nachgedacht habe.
Paul Tomblin