Blok statyczny w Javie nie został wykonany

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);
    }
}

Wiem, że staticblok wykonywany po załadowaniu klasy. Ale w tym przypadku zmienną instancji wewnątrz klasy Mnojest final, ponieważ staticblok nie jest wykonywany.

Dlaczego to jest takie? A gdybym usunął final, czy to zadziała?

Która pamięć zostanie przydzielona jako pierwsza, static finalzmienna czy staticblok?

Jeśli z powodu finalmodyfikatora dostępu klasa nie jest ładowana, to w jaki sposób zmienna może uzyskać pamięć?

Sthita
źródło
1
Jaki jest dokładny błąd i komunikat, który otrzymujesz?
Patashu
@Patashu, nie ma błędu, to wątpliwość
Sthita

Odpowiedzi:

134
  1. static final intPole jest stałą czasu kompilacji , a jego wartość jest sztywno do klasy przeznaczenia bez odniesienia do jego pochodzenia;
  2. dlatego twoja główna klasa nie wyzwala wczytania klasy zawierającej pole;
  3. w związku z tym statyczny inicjator w tej klasie nie jest wykonywany.

W szczególności, skompilowany kod bajtowy odpowiada temu:

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

Zaraz po usunięciu finalnie jest to już stała czasu kompilacji, a specjalne zachowanie opisane powyżej nie ma zastosowania. MnoKlasa jest ładowana zgodnie z oczekiwaniami i jego statyczna initializer wykonany.

Marko Topolnik
źródło
1
Ale w takim razie w jaki sposób jest obliczana wartość ostatniej zmiennej w klasie bez ładowania klasy?
Sumit Desai
18
Cała ocena odbywa się w czasie kompilacji, a wynik końcowy jest zakodowany na stałe we wszystkich miejscach, które odwołują się do zmiennej.
Marko Topolnik
1
Tak więc, jeśli zamiast prymitywnej zmiennej jest to jakiś obiekt, to takie twarde kodowanie nie będzie możliwe. Prawda? Czy w takim przypadku ta klasa zostanie załadowana, a blok statyczny zostanie wykonany?
Sumit Desai
2
Marko, wątpliwość Sumita jest słuszna, także jeśli zamiast prymitywnego jest to jakiś obiekt, to takie twarde kodowanie nie będzie możliwe. Prawda? Czy w takim przypadku ta klasa zostanie załadowana, a blok statyczny zostanie wykonany?
Sthita
8
@SumitDesai Dokładnie, działa to tylko dla wartości pierwotnych i literałów ciągów. Aby uzyskać szczegółowe informacje, przeczytaj odpowiedni rozdział w specyfikacji języka Java
Marko Topolnik
8

Powodem, dla którego klasa nie jest załadowany jest to, że VALjest final i to jest inicjowany z wyrażeniem stałym (9090). Jeśli i tylko wtedy, gdy te dwa warunki są spełnione, stała jest oceniana w czasie kompilacji i „zakodowana” w razie potrzeby.

Aby zapobiec ocenie wyrażenia w czasie kompilacji (i zmusić maszynę JVM do załadowania Twojej klasy), możesz:

  • usuń ostatnie słowo kluczowe:

    static int VAL = 9090; //not a constant variable any more
    
  • lub zmień wyrażenie po prawej stronie na coś niestałego (nawet jeśli zmienna jest nadal ostateczna):

    final static int VAL = getInt(); //not a constant expression any more
    static int getInt() { return 9090; }
    
asylias
źródło
5

Jeśli zobaczysz wygenerowany kod bajtowy za pomocą javap -v Test.class, main () wygląda tak:

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        

W " 11: sipush 9090" można wyraźnie zobaczyć, że statyczna wartość końcowa jest używana bezpośrednio, ponieważ Mno.VAL jest stałą czasową kompilacji. Dlatego nie jest wymagane ładowanie klasy Mno. Stąd statyczny blok Mno nie jest wykonywany.

Możesz wykonać blok statyczny, ręcznie ładując Mno, jak poniżej:

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
źródło
1
  1. Właściwie nie rozszerzyłeś tej klasy Mno, więc po uruchomieniu kompilacji wygeneruje stałą o zmiennej VAL, a gdy rozpocznie się wykonywanie, gdy ta zmienna jest potrzebna, jej obciążenie pochodzi z pamięci. Nie jest więc wymagane, aby odwołanie do klasy nie było wykonywane.

  2. jeśli klasa Arozszerza klasę Mno, blok statyczny jest zawarty w klasie, Ajeśli to zrobisz, wykonywany jest ten blok statyczny. Na przykład..

    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
źródło
0

O ile wiem, zostanie to wykonane w kolejności pojawiania się. Na przykład :

 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";
     }
 }

wydrukuje

  trace init1
  trace middle
  trace init2

Właśnie to przetestowałem i statyka jest inicjalizowana (=> print), gdy klasa "Statique" jest faktycznie używana i "wykonywana" w innym fragmencie kodu (w moim przypadku zrobiłem "new Statique ()".

Fabyen
źródło
2
Otrzymujesz ten wynik, ponieważ ładujesz Statiqueklasę wykonując new Statique(). W zadanym pytaniu Mnoklasa w ogóle nie jest załadowana.
RAS
@Fabyen, jeśli tworzę obiekt Mno w klasie testowej w ten sposób: Mno anc = New Mno (); to jest w porządku, ale obecny scenariusz nie robię tego, mam wątpliwości, czy usuwam wersję ostateczną, a następnie blok statyczny jest wykonywany dobrze, w przeciwnym razie nie jest wykonywany, dlaczego tak?
Sthita
1
Tak, odpowiedź poniżej jest idealna. W kodzie bajtowym klasy Main.class (przy użyciu Mno.VAL), 9090 znajduje się na stałe. Usuń final, skompiluj, a następnie użyj javap Main, zobaczysz getstatic # 16; // Pole Statique.VAL: ja . Przywróć wersję ostateczną, skompiluj, a następnie użyj javap Main, zobaczysz sipush 9090 .
Fabyen
1
Ponieważ jest zakodowany na stałe w Main.class, nie ma powodu, aby ładować klasę MNO, stąd nie ma statycznej inicjalizacji.
Fabyen
Odpowiada to na drugie pytanie: „Która pamięć zostanie przydzielona jako pierwsza, statyczna zmienna końcowa czy statyczny blok?” (kolejność leksykalna)
Hauke ​​Ingmar Schmidt