Jak modyfikator static wpływa na ten kod?

109

Oto mój kod:

class A {
    static A obj = new A();
    static int num1;
    static int num2=0;

    private A() {
        num1++;
        num2++;
    }
    public static A getInstance() {
        return obj;
    }
}

public class Main{
    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

Wynik jest 1 0, ale nie mogę zrozumieć.

Czy ktoś może mi to wyjaśnić?

lirui
źródło
10
Fajne pytanie! Czego powinniśmy się z tego nauczyć: nie rób tego! ;)
zły

Odpowiedzi:

116

W Javie mają miejsce dwie fazy: 1. Identyfikacja, 2. Wykonanie

  1. W fazie identyfikacji wszystkie zmienne statyczne są wykrywane i inicjowane z wartościami domyślnymi.

    Więc teraz wartości są następujące:
    A obj=null
    num1=0
    num2=0

  2. Druga faza, wykonanie , zaczyna się od góry do dołu. W Javie wykonanie rozpoczyna się od pierwszych statycznych elementów członkowskich.
    Tutaj znajduje się twoja pierwsza zmienna statyczna static A obj = new A();, więc najpierw utworzy obiekt tej zmiennej i wywoła konstruktor, stąd wartość num1i stanie num2się 1.
    A potem znowu static int num2=0;zostanie wykonany, co sprawia num2 = 0;.

Teraz załóżmy, że twój konstruktor wygląda tak:

 private A(){
    num1++;
    num2++;
    System.out.println(obj.toString());
 }

Spowoduje to rzucenie NullPointerException który objnadal nie ma odniesienia class A.

Shoaib Chikate
źródło
11
Przedłużę: przesuń linię static A obj = new A();poniżej static int num2=0;i powinieneś dostać 1 i 1.
Thomas
2
To, co nadal mnie wprawia w zakłopotanie, to fakt, że chociaż num1 nie ma jawnej inicjalizacji, JEST (niejawnie) zainicjowany z wartością 0. Naprawdę nie powinno być żadnej różnicy między jawną i niejawną inicjalizacją ...
isnot2bad
@ isnot2bad „niejawna inicjalizacja” ma miejsce jako część deklaracji. Deklaracja stanie przed przypisanie nieważne jakiej kolejności przedstawić je w. A obj = new A(); int num1; int num2 = 0;Zostaje przekształcony w ten sposób: A obj; int num1; int num2; obj = new A(); num2 = 0;. Java robi to, więc num1, num2są definiowane przez czas dotarcia do new A()konstruktora.
Hans Z
31

To, co staticoznacza modyfikator, gdy jest stosowany do deklaracji zmiennej, to fakt, że zmienna jest zmienną klasy, a nie zmienną instancji. Innymi słowy ... jest tylko jedna num1zmienna i tylko jedna num2zmienna.

(Poza tym: zmienna statyczna jest podobna do zmienna globalna w niektórych innych językach, z tą różnicą, że jej nazwa nie jest widoczna wszędzie. Nawet jeśli jest zadeklarowana jako a public static, niekwalifikowana nazwa jest widoczna tylko wtedy, gdy jest zadeklarowana w bieżącej klasie lub nadklasie lub jeśli jest importowany przy użyciu importu statycznego. Na tym polega różnica. Prawdziwy globalny jest wszędzie widoczny bez kwalifikacji).

Więc kiedy odnoszą się do obj.num1i obj.num2, w rzeczywistości odnosi się do tych zmiennych statycznych, których nazwy są prawdziwe A.num1i A.num2. I podobnie, gdy konstruktor zwiększa wartość num1inum2 , zwiększa te same zmienne (odpowiednio).

Myląca kwestia w twoim przykładzie dotyczy inicjalizacji klasy. Klasa jest inicjowana przez pierwszą domyślną inicjalizację wszystkich zmiennych statycznych, a następnie wykonywanie zadeklarowanych inicjatorów statycznych (i bloków inicjatorów statycznych) w kolejności, w jakiej pojawiają się w klasie. W tym przypadku masz to:

static A obj = new A();
static int num1;
static int num2=0;

Dzieje się tak:

  1. Statyka zaczyna się od domyślnych wartości początkowych; A.objjest nulli A.num1/ A.num2są zerem.

  2. Pierwsza deklaracja ( A.obj) tworzy wystąpienie A()i konstruktor dla Ainkrementacji A.num1i A.num2. Gdy deklaracja zakończy się, A.num1i A.num2są oba 1, i A.objodwołuje się do nowo utworzonego Awystąpienia.

  3. Druga deklaracja ( A.num1) nie ma inicjatora, więc A.num1się nie zmienia.

  4. Trzecia deklaracja ( A.num2) ma inicjator, który przypisuje zero do A.num2.

Tak więc pod koniec inicjalizacji klasy A.num1jest 1i A.num2jest0 ... i to właśnie pokazują twoje instrukcje print.

To mylące zachowanie jest tak naprawdę spowodowane faktem, że tworzysz instancję przed zakończeniem inicjalizacji statycznej i że konstruktor, którego używasz, zależy od statycznej, która nie została jeszcze zainicjowana, i modyfikuje ją. To coś, czego powinieneś unikać w prawdziwym kodzie.

Stephen C.
źródło
16

1,0 jest poprawne.

Kiedy klasa jest ładowana, wszystkie dane statyczne są inicjowane lub deklarowane. Domyślnie int wynosi 0.

  • tworzony jest pierwszy A. Num1 i num2 stają się 1 i 1
  • niż static int num1;nic nie robi
  • niż static int num2=0;to zapisuje 0 do num2
Leonidos
źródło
9

Wynika to z kolejności statycznych inicjatorów. Wyrażenia statyczne w klasach są oceniane w kolejności odgórnej.

Pierwszym wywoływanym konstruktorem jest konstruktor A, który ustawia num1i num2oba na 1:

static A obj = new A();

Następnie,

static int num2=0;

jest wywoływana i ponownie ustawia num2 = 0.

Dlatego num1jest 1 i num2wynosi 0.

Na marginesie, konstruktor nie powinien modyfikować zmiennych statycznych, to jest bardzo zły projekt. Zamiast tego wypróbuj inne podejście do implementacji Singletona w Javie .

Domi
źródło
6

Sekcję w JLS można znaleźć: §12.4.2 .

Szczegółowa procedura inicjalizacji:

9. Następnie wykonaj inicjalizatory zmiennych klas i statyczne inicjatory klasy lub inicjatory pól interfejsu w porządku tekstowym, tak jakby były pojedynczym blokiem, z wyjątkiem końcowych zmiennych klasy i pól interfejsów, których wartości są kompilowane -stałe czasowe są inicjalizowane jako pierwsze

Tak więc trzy zmienne statyczne zostaną zainicjowane jedna po drugiej w kolejności tekstowej.

Więc

static A obj = new A();
//num1 = 1, num2 = 1;
static int num1;
//this is initilized first, see below.
static int num2=0;
//num1 = 1, num2 = 0;

Jeśli zmienię zamówienie na:

static int num1;
static int num2=0;
static A obj = new A();

Wynik będzie 1,1.

Zauważ, że static int num1;nie jest zmienną inicjalizującą, ponieważ ( §8.3.2 ):

Jeśli deklarator pola zawiera inicjator zmiennej, to ma semantykę przypisania (§15.26) do zadeklarowanej zmiennej oraz: Jeśli deklarator dotyczy zmiennej klasy (czyli pola statycznego), to inicjalizatorem zmiennej jest oceniane i przypisanie wykonywane dokładnie raz, podczas inicjowania klasy

Ta zmienna klasy jest inicjowana podczas tworzenia klasy. Dzieje się to najpierw ( §4.12.5 ).

Każda zmienna w programie musi mieć wartość, zanim zostanie użyta jej wartość: Każda zmienna klasy, zmienna instancji lub składnik tablicy jest inicjowana wartością domyślną podczas jej tworzenia (§15.9, §15.10): W przypadku bajtu typu wartość domyślna wynosi zero, czyli wartość (bajt) 0. Dla typu short wartością domyślną jest zero, czyli wartość (short) 0. Dla typu int wartością domyślną jest zero, to znaczy 0. Dla typu long, wartością domyślną jest zero, czyli 0L. Dla typu float wartością domyślną jest dodatnie zero, czyli 0,0f. W przypadku typu double wartością domyślną jest dodatnie zero, czyli 0,0d. W przypadku typu char wartością domyślną jest znak null, czyli „\ u0000”. Dla typu boolean wartością domyślną jest false. Dla wszystkich typów odwołań (§4.3) wartością domyślną jest null.

StarPinkER
źródło
2

Może pomoże pomyśleć o tym w ten sposób.

Klasy to plany obiektów.

Obiekty mogą mieć zmienne, gdy są tworzone.

Klasy mogą również mieć zmienne. Są one deklarowane jako statyczne. Są więc ustawiane w klasie, a nie instancjach obiektów.

Możesz mieć tylko jedną z dowolnej klasy w aplikacji, więc jest to coś w rodzaju globalnej pamięci masowej specjalnie dla tej klasy. Dostęp do tych zmiennych statycznych można oczywiście uzyskać i modyfikować z dowolnego miejsca w aplikacji (zakładając, że są publiczne).

Oto przykład klasy „Dog”, która używa zmiennej statycznej do śledzenia liczby utworzonych instancji.

Klasa „Pies” to chmura, podczas gdy pomarańczowe pola to instancje „Pies”.

Klasa psów

Czytaj więcej

Mam nadzieję że to pomoże!

Jeśli masz ochotę na ciekawostki, pomysł ten został po raz pierwszy przedstawiony przez Plato

Goran
źródło
1

Słowo kluczowe static jest używane w Javie głównie do zarządzania pamięcią. Możemy zastosować statyczne słowo kluczowe ze zmiennymi, metodami, blokami i zagnieżdżoną klasą. Słowo kluczowe static należy do klasy niż do instancji klasy. Krótkie wyjaśnienie dotyczące słowa kluczowego static:

http://www.javatpoint.com/static-keyword-in-java

rachana
źródło
0

Wiele z powyższych odpowiedzi jest poprawnych. Ale tak naprawdę, aby zilustrować, co się dzieje, wprowadziłem poniżej kilka drobnych modyfikacji.

Jak wspomniano wielokrotnie powyżej, dzieje się tak, że instancja klasy A jest tworzona przed pełnym załadowaniem klasy A. Zatem to, co jest uważane za normalne „zachowanie”, nie jest obserwowane. Nie różni się to zbytnio od wywoływania metod z konstruktora, który można przesłonić. W takim przypadku zmienne instancji mogą nie być w stanie intuicyjnym. W tym przykładzie zmienne klasowe nie są w stanie intuicyjnym.

class A {
    static A obj = new A();
    static int num1;
    static int num2;
    static {
        System.out.println("Setting num2 to 0");
        num2 = 0;
    }

    private A() {
        System.out.println("Constructing singleton instance of A");
        num1++;
        num2++;
    }

    public static A getInstance() {
        return obj;
    }
}

public class Main {

    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

Wyjście jest

Constructing singleton instance of A
Setting num2 to 0
1
0
w25r
źródło
0

java nie inicjuje wartości żadnego statycznego lub niestatycznego elementu członkowskiego danych, dopóki nie zostanie wywołany, ale go utworzy.

więc tutaj, gdy num1 i num2 zostaną wywołane w main, to zostanie zainicjalizowane wartościami

num1 = 0 + 1; i

num2 = 0;

deepak tiwari
źródło