Statischer Block in Java nicht ausgeführt

87
class Test {
    public static void main(String arg[]) {    
        System.out.println("**MAIN METHOD");
        System.out.println(Mno.VAL); // SOP(9090);
        System.out.println(Mno.VAL + 100); // SOP(9190);
    }

}

class Mno {
    final static int VAL = 9090;
    static {
        System.out.println("**STATIC BLOCK OF Mno\t: " + VAL);
    }
}

Ich weiß, dass ein staticBlock ausgeführt wurde, wenn die Klasse geladen wurde. In diesem Fall ist die Instanzvariable innerhalb der Klasse Mnojedoch so final, dass der staticBlock nicht ausgeführt wird.

Warum ist das so? Und wenn ich das entfernen finalwürde, würde es gut funktionieren?

Welcher Speicher wird zuerst zugewiesen, die static finalVariable oder der staticBlock?

Wenn finaldie Klasse aufgrund des Zugriffsmodifikators nicht geladen wird, wie kann die Variable dann Speicher erhalten?

Sthita
quelle
1
Was ist der genaue Fehler und die Meldung, die Sie erhalten?
Patashu
@ Patashu, es gibt keinen Fehler, es ist ein Zweifel
Sthita

Antworten:

132
  1. Ein static final intFeld ist eine Konstante zur Kompilierungszeit und sein Wert wird ohne Bezugnahme auf seinen Ursprung fest in die Zielklasse codiert.
  2. Daher löst Ihre Hauptklasse nicht das Laden der Klasse aus, die das Feld enthält.
  3. Daher wird der statische Initialisierer in dieser Klasse nicht ausgeführt.

Im Einzelnen entspricht der kompilierte Bytecode dem:

public static void main(String arg[]){    
    System.out.println("**MAIN METHOD");
    System.out.println(9090)
    System.out.println(9190)
}

Sobald Sie entfernen final, handelt es sich nicht mehr um eine Kompilierungszeitkonstante, und das oben beschriebene spezielle Verhalten gilt nicht mehr. Die MnoKlasse wird wie erwartet geladen und ihr statischer Initialisierer wird ausgeführt.

Marko Topolnik
quelle
1
Aber wie wird dann der Wert der endgültigen Variablen in der Klasse ausgewertet, ohne die Klasse zu laden?
Sumit Desai
18
Alle Auswertungen erfolgen zur Kompilierungszeit und das Endergebnis wird an allen Stellen fest codiert, die auf die Variable verweisen.
Marko Topolnik
1
Wenn es sich also anstelle einer primitiven Variablen um ein Objekt handelt, ist eine solche Hardcodierung nicht möglich. Ist es nicht? Wird in diesem Fall diese Klasse geladen und der statische Block ausgeführt?
Sumit Desai
2
Marko, Sumits Zweifel ist auch richtig, wenn es sich nicht um ein primitives, sondern um ein Objekt handelt, ist eine solche Hardcodierung nicht möglich. Ist es nicht? Wird in diesem Fall diese Klasse geladen und der statische Block ausgeführt?
Sthita
8
@SumitDesai Genau das funktioniert nur für primitive Werte und String-Literale. Für weitere
Informationen
8

Der Grund , warum die Klasse nicht geladen ist , dass VAList final und es wird mit initialisiert einen konstanten Ausdruck (9090). Wenn und nur wenn diese beiden Bedingungen erfüllt sind, wird die Konstante zur Kompilierungszeit ausgewertet und bei Bedarf "fest codiert".

Um zu verhindern, dass der Ausdruck zur Kompilierungszeit ausgewertet wird (und die JVM Ihre Klasse laden lässt), können Sie entweder:

  • Entfernen Sie das letzte Schlüsselwort:

    static int VAL = 9090; //not a constant variable any more
  • oder ändern Sie den Ausdruck auf der rechten Seite in einen nicht konstanten Ausdruck (auch wenn die Variable noch endgültig ist):

    final static int VAL = getInt(); //not a constant expression any more
    static int getInt() { return 9090; }
    
Assylien
quelle
5

Wenn Sie den generierten Bytecode mit sehen javap -v Test.class, wird main () wie folgt ausgegeben:

public static void main(java.lang.String[]) throws java.lang.Exception;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String **MAIN METHOD
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: sipush        9090
        14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: sipush        9190
        23: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        26: return        

Sie können in " 11: sipush 9090" deutlich sehen, dass der statische Endwert direkt verwendet wird, da Mno.VAL eine Kompilierungszeitkonstante ist. Daher ist es nicht erforderlich, die Mno-Klasse zu laden. Daher wird der statische Block von Mno nicht ausgeführt.

Sie können den statischen Block ausführen, indem Sie Mno wie folgt manuell laden:

class Test{
    public static void main(String arg[]) throws Exception {
        System.out.println("**MAIN METHOD");
        Class.forName("Mno");                 // Load Mno
        System.out.println(Mno.VAL);
        System.out.println(Mno.VAL+100);
    }

}

class Mno{
    final static int VAL=9090;
    static{
        System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
    }
}
Xolve
quelle
1
  1. Tatsächlich haben Sie diese Mno-Klasse nicht erweitert, sodass beim Start der Kompilierung eine Konstante der Variablen VAL generiert wird und beim Start der Ausführung, wenn diese Variable benötigt wird, ihre Last aus dem Speicher stammt. Es ist also nicht erforderlich, dass Ihre Klasse referenziert, damit der statische Bock nicht ausgeführt wird.

  2. Wenn die Klasse Adie Klasse erweitert Mno, wird der statische Block in die Klasse aufgenommen. AWenn Sie dies tun, wird dieser statische Block ausgeführt. Beispielsweise..

    public class A extends Mno {
    
        public static void main(String arg[]){    
            System.out.println("**MAIN METHOD");
            System.out.println(Mno.VAL);//SOP(9090);
            System.out.println(Mno.VAL+100);//SOP(9190);
        }
    
    }
    
    class Mno {
        final static int VAL=9090;
        static {
            System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
        }
    }
    
Ketan_Patel
quelle
0

Soweit ich weiß, wird es in der Reihenfolge seines Auftretens ausgeführt. Zum Beispiel :

 public class Statique {
     public static final String value1 = init1();

     static {
         System.out.println("trace middle");
     }
     public static final String value2 = init2();


     public static String init1() {
         System.out.println("trace init1");
         return "1";
     }
     public static String init2() {
         System.out.println("trace init2");
         return "2";
     }
 }

wird gedruckt

  trace init1
  trace middle
  trace init2

Ich habe es gerade getestet und die Statik wird initialisiert (=> print), wenn die Klasse "Statique" tatsächlich in einem anderen Code verwendet und "ausgeführt" wird (in meinem Fall habe ich "new Statique ()" ausgeführt.

Fabyen
quelle
2
Sie erhalten diese Ausgabe, weil Sie die StatiqueKlasse auf diese Weise laden new Statique(). Während der gestellten Frage wird die MnoKlasse überhaupt nicht geladen.
RAS
@Fabyen, wenn ich ein Objekt von Mno in einer Testklasse wie folgt erstelle: Mno anc = New Mno (); dann ist es in Ordnung, aber aktuelles Szenario mache ich das nicht, mein Zweifel ist, wenn ich final entferne, dann wird der statische Block gut ausgeführt, sonst wird er nicht ausgeführt, warum so?
Sthita
1
Ja, die Antwort unten ist perfekt. Im Bytecode von Main.class (unter Verwendung von Mno.VAL) ist 9090 fest codiert. Entfernen Sie final, kompilieren Sie und verwenden Sie dann javap Main. Sie sehen getstatic # 16; // Feld Statique.VAL: I . Setzen Sie final zurück, kompilieren Sie und verwenden Sie dann javap Main. Sie werden sipush 9090 sehen .
Fabyen
1
Da es in Main.class fest codiert ist, gibt es keinen Grund, Klasse MNO zu laden, daher gibt es keine statische Initialisierung.
Fabyen
Dies beantwortet die zweite Frage: "Welcher Speicher wird zuerst zugewiesen, die statische Endvariable oder der statische Block?" (lexikalische Reihenfolge)
Hauke ​​Ingmar Schmidt