Pytanie Dziwne zachowanie Java z kwalifikatorami statycznymi i końcowymi [duplicate]


To pytanie już zawiera odpowiedź:

W naszym zespole znaleźliśmy dziwne zachowanie, w którym używaliśmy obu static i final kwalifikatory. To jest nasza klasa testowa:

public class Test {

    public static final Test me = new Test();
    public static final Integer I = 4;
    public static final String S = "abc";

    public Test() {
        System.out.println(I);
        System.out.println(S);
    }

    public static Test getInstance() { return me; }

    public static void main(String[] args) {
        Test.getInstance();
    }
} 

Kiedy uruchamiamy main metoda, otrzymujemy wynik:

null
abc

Rozumiem, jeśli to napisał null wartości oba razy, ponieważ kod statycznych członków klasy jest wykonywany od góry do dołu.

Czy ktoś może wyjaśnić, dlaczego to zachowanie się dzieje?


76
2017-09-12 10:22


pochodzenie


Związane z stackoverflow.com/questions/4446088/... (i stackoverflow.com/questions/2423376/...) - Tunaki
Możesz spojrzeć na odpowiedzi to pytanie . mam nadzieję, że to pomoże. - Sagar Singh
Heh. Kolejne zamieszanie związane z uprzywilejowanymi typami Java. - imallett
Oznaczony teraz jako duplikat ... oznacza, że efekt biuletynu? - Didier L


Odpowiedzi:


Oto kroki podejmowane podczas uruchamiania programu:

  1. Przed main można uruchomić, Test klasa musi zostać zainicjalizowana przez uruchomienie inicjalizatorów statycznych w kolejności ich pojawiania się.
  2. Aby zainicjować me pole, zacznij wykonywanie new Test().
  3. Wydrukuj wartość I. Ponieważ typem pola jest Integer, co wydaje się stałą kompilacją 4 staje się wartością obliczoną (Integer.valueOf(4)). Inicjator tego pola jeszcze się nie uruchomił, drukując wartość początkową null.
  4. Wydrukuj wartość S. Ponieważ jest on inicjalizowany ze stałą czasu kompilacji, ta wartość jest wypalana na stronie referencyjnej, podczas drukowania abc.
  5. new Test() kończy, teraz inicjalizator dla I wykonuje.

Lekcja: jeśli polegasz na niecierpliwie zainicjowanych statycznych singletonach, umieść deklarację singleton jako ostatnią deklarację pola statycznego lub ucieknij do statycznego bloku inicjalizatora, który występuje po wszystkich innych statycznych deklaracjach. To sprawi, że klasa będzie w pełni zainicjowana na kod budowy singletonu.


109
2017-09-12 10:28





S jest stałą czasu kompilacji, zgodnie z regułami JLS 15,28. Tak więc każde wystąpienie S w kodzie jest zastąpiony wartością znaną podczas kompilacji.

Jeśli zmienisz typ I do intzobaczysz to samo.


71
2017-09-12 10:25





Masz dziwne zachowanie ze względu na Integer typ danych. Jeżeli chodzi o JLS 12.4.2 pola statyczne są inicjowane w kolejności, w jakiej je zapisujesz, ale stałe kompilacji są najpierw inicjowane.

Jeśli nie używasz typu opakowania Integer ale int wpisz, otrzymujesz pożądane zachowanie.


21
2017-09-12 10:38





Twój Test kompiluje się w:

public class Test {

    public static final Test me;
    public static final Integer I;
    public static final String S = "abc";

    static {
        me = new Test();
        I = Integer.valueOf(4);
    }

    public Test() {
        System.out.println(I);
        System.out.println("abc");
    }

    public static Test getInstance() { return me; }

    public static void main(String[] args) {
        Test.getInstance();
    }
}

Jak widać, konstruktor dla Test zostanie wywołany wcześniej I jest zainicjowany. Właśnie dlatego drukuje "null" dla I. Jeśli chcesz zamienić deklarację na me i I, uzyskasz oczekiwany wynik, ponieważ I zostanie zainicjowany przed wywołaniem konstruktora. Możesz również zmienić typ dla I od Integer do int.

Bo 4 musi uzyskać autoboxed (tj. zawinięty w Integer obiekt), nie jest stałą w czasie kompilacji i jest częścią statycznego bloku inicjalizatora. Jednakże, jeśli typ był int, numer 4 byłaby stała czasu kompilacji, więc nie musiałaby być jawnie inicjowana. Bo "abc" jest stała czasu kompilacji, wartość Sjest drukowany zgodnie z oczekiwaniami.

Jeśli chcesz wymienić,

public static final String S = "abc";

z,

public static final String S = new String("abc");

Wtedy zauważysz wyjście S jest "null" także. Dlaczego tak się dzieje? Z tego samego powodu, dlaczego I także wyjścia "null". Pola takie jak te, które mają dosłowne, stałe wartości (tj nie rób potrzebujesz autoboxing, jak String) są przypisane do "ConstantValue" atrybut po skompilowaniu, co oznacza, że ​​ich wartość można rozwiązać po prostu patrząc na pulę stałą klasy, bez potrzeby uruchamiania jakiegokolwiek kodu.


13
2017-09-12 15:41



Pewny tego? oczekiwałbym System.out.println("abc");, zamiast System.out.println(S);, ponieważ kompilator inline kompiluje stałe czasowe. - Tom
Masz rację. W pierwszym przypadku będzie ldc instrukcja, a nie a getstatic. Właśnie to próbowałem osiągnąć ostatnim zdaniem, przypuszczam, że powinienem to wyjaśnić. - Martin Tuskevicius
Twój kod powinien pokazać, że skoro powiedziałeś, że OP zostanie w to skompilowany. - Tom
@Tom To rozróżnienie bez różnicy. S to inna nazwa "abc". - user207421
@ EJP Może nie tutaj, ponieważ deklaracja i dostęp są w tej samej klasie, ale ważne jest, aby się różnić, jeśli są w różnych klasach. - Tom