Як впливає статичний модифікатор на цей код?


109

Ось мій код:

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

Вихід є 1 0, але я не можу зрозуміти.

Може хтось мені це пояснить?


10
Приємне запитання! Що ми повинні з цього навчитися: Не робіть цього! ;)
isnot2bad

Відповіді:


116

У Java проходять дві фази: 1. Ідентифікація, 2. Виконання

  1. На етапі ідентифікації всі статичні змінні виявляються та ініціалізуються зі значеннями за замовчуванням.

    Тож тепер значення такі:
    A obj=null
    num1=0
    num2=0

  2. Другий етап, виконання , починається зверху вниз. У Java виконання починається з перших статичних членів.
    Тут ваша перша статична змінна static A obj = new A();, тому спочатку вона створить об'єкт цієї змінної та викличе конструктор, отже, значення num1і num2стає 1.
    І тоді, знову ж таки, static int num2=0;буде виконано, що робить num2 = 0;.

Тепер, припустимо, ваш конструктор такий:

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

Це кине а NullPointerExceptionяк objдосі не отримав посилання на class A.


11
Я продовжу: перемістіть рядок static A obj = new A();нижче, static int num2=0;і ви повинні отримати 1 і 1.
Томас

2
Що все ще мене бентежить, це той факт, що, хоча num1 не має явної ініціалізації, він IS (неявно) ініціалізований з 0. Справді не повинно бути різниці між явною та неявною ініціалізацією ...
isnot2bad

@ isnot2bad "неявна ініціалізація" відбувається як частина декларації. Декларація не відбувається до присвоєння незалежно від того , в якому порядку ви уявляєте їх. A obj = new A(); int num1; int num2 = 0;Перетворювані це: A obj; int num1; int num2; obj = new A(); num2 = 0;. Java робить це так num1, num2, визначається часом, коли ви досягнете new A()конструктора.
Ганс Z

31

Що staticмодифікатор означає при застосуванні до оголошення змінної, це те, що змінна є змінною класу, а не змінною екземпляра. Іншими словами ... існує лише одна num1змінна і лише одна num2змінна.

(Убік: статична змінна схожа на глобальну змінну в деяких інших мовах, за винятком того, що її ім’я не видно скрізь. Навіть якщо вона оголошена як public static, некваліфіковане ім’я видно лише в тому випадку, якщо вона оголошена в поточному класі або надкласовій або якщо він імпортований за допомогою статичного імпорту. Це відмінність. Справжній глобальний світ видно без будь-якої кваліфікації.)

Тому , коли ви дивитеся obj.num1і obj.num2ви насправді маючи на увазі на статичні змінні, речові позначення A.num1і A.num2. І також, коли конструктор збільшується num1і num2, він збільшує ті самі змінні (відповідно).

Заплутана зморшка у вашому прикладі полягає в ініціалізації класу. Клас ініціалізується спочатку за замовчуванням ініціалізацією всіх статичних змінних, а потім виконанням оголошених статичних ініціалізаторів (та статичних блоків ініціалізаторів) у тому порядку, як вони відображаються в класі. У цьому випадку у вас є таке:

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

Буває так:

  1. Статика починається зі своїх стандартних початкових значень; A.objє nullі A.num1/ A.num2є нулем.

  2. Перше оголошення ( A.obj) створює екземпляр A()і конструктор для Aзбільшення A.num1та A.num2. Коли декларація завершується A.num1і A.num2є обома 1, і A.objпосилається на щойно побудований Aекземпляр.

  3. У другій декларації ( A.num1) немає ініціалізатора, тому A.num1не змінюється.

  4. Третя заява ( A.num2) має ініціалізатор, який присвоює нуль A.num2.

Таким чином, наприкінці класу ініціалізація A.num1є 1і A.num2є 0... і ось що показують ваші друковані заяви.

Ця заплутана поведінка насправді зводиться до того, що ви створюєте екземпляр до завершення статичної ініціалізації, і що конструктор, який ви використовуєте, залежить від та змінює статику, яку ще слід ініціалізувати. Це те, чого вам слід уникати робити в реальному коді.


16

1,0 правильно.

Коли завантажується клас, всі статичні дані ініціалізуються в оді, вони оголошуються. За замовчуванням int дорівнює 0.

  • спочатку створюється А. num1 і num2 стають 1 і 1
  • ніж static int num1;нічого не робить
  • ніж static int num2=0;це пише 0 до num2

9

Це обумовлено порядком статичних ініціалізаторів. Статичні вирази в класах оцінюються в порядку зверху вниз.

Першим, хто викликається, є конструктор A, який встановлює num1і num2обидва на 1:

static A obj = new A();

Тоді,

static int num2=0;

викликається і знову встановлює num2 = 0.

Ось чому num1дорівнює 1 і num2дорівнює 0.

Як бічна примітка, конструктор не повинен змінювати статичні змінні, це дуже погана конструкція. Натомість спробуйте інший підхід до реалізації Singleton в Java .


6

Розділ у JLS можна знайти: §12.4.2 .

Детальний порядок ініціалізації:

9.Надалі, виконайте або ініціалізатори змінної класу та статичні ініціалізатори класу, або полеві ініціалізатори інтерфейсу, в текстовому порядку, як би вони були єдиним блоком, за винятком змінних остаточного класу та полі інтерфейсів, значення яких компілюються -константи часу ініціалізуються спочатку

Таким чином, три статичні змінні будуть ініціалізовані одна за одною у текстовому порядку.

Так

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;

Якщо я зміню замовлення на:

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

Результат буде 1,1.

Зауважте, що static int num1;ініціалізатор змінної не є, оскільки ( § 8.3.2 ):

Якщо декларатор поля містить ініціалізатор змінної, то він має семантику присвоєння оголошеній змінній (§15.26) та: Якщо декларатор призначений для змінної класу (тобто статичного поля), то ініціалізатором змінної є оцінюється, а завдання виконується рівно один раз, коли ініціалізується клас

І ця змінна категорія ініціалізується при створенні класу. Це відбувається спочатку ( §4.12.5 ).

Кожна змінна в програмі повинна мати значення перед використанням її значення: Кожна змінна класу, змінна інстанція або компонент масиву ініціалізується зі значенням за замовчуванням, коли воно створюється (§15.9, §15.10): Для байта типу значення за замовчуванням дорівнює нулю, тобто значення (байт) 0. Для типу short, значення за замовчуванням дорівнює нулю, тобто значення (short) 0. Для типу int значення за замовчуванням дорівнює нулю, тобто 0. Для типу long значення за замовчуванням дорівнює нулю, тобто 0L. Для типу float значення за замовчуванням є додатним нулем, тобто 0,0f. Для типу double - значення за замовчуванням - це додатний нуль, тобто 0,0d. Для типу char типовим значенням є нульовий символ, тобто '\ u0000'. Для типу boolean значення за замовчуванням - false. Для всіх типів посилань (§4.3) значення за замовчуванням є нульовим.


2

Можливо, це допоможе подумати про це таким чином.

Заняття - креслення для об'єктів.

Об'єкти можуть мати змінні, коли вони інстанціюються.

Класи також можуть мати змінні. Вони оголошені статичними. Таким чином вони встановлюються на клас, а не на об'єкти екземплярів.

У програмі ви можете мати лише один клас будь-якого класу, так що це схоже на глобальне сховище спеціально для цього класу. Ці статичні змінні, звичайно, можуть бути доступні та змінені з будь-якої точки вашої програми (за умови, що вони є загальнодоступними).

Ось і приклад класу "Dog", який використовує статичну змінну для відстеження кількості створених ними примірників.

Клас "Собака" - це хмара, а помаранчеві поля - це екземпляри "Собаки".

Клас собак

читати більше

Сподіваюся, це допомагає!

Якщо ви відчуваєте, як якісь дрібниці, цю ідею вперше ввів Платон


1

Статичне ключове слово використовується в Java в основному для управління пам'яттю. Ми можемо застосувати статичне ключове слово зі змінними, методами, блоками та вкладеним класом. Статичне ключове слово належить до класу, ніж екземпляр класу. Для короткого пояснення щодо статичного ключового слова:

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


0

Багато відповідей вище є правильними. Але насправді для ілюстрації того, що відбувається, я вніс кілька невеликих модифікацій нижче.

Як вже згадувалося вище, те, що відбувається - це екземпляр класу A, який створюється до повного завантаження класу A. Тож те, що вважається нормальною 'поведінкою', не спостерігається. Це не надто відрізняється від виклику методів з конструктора, який можна переотримати. У цьому випадку змінні екземпляри можуть перебувати не в інтуїтивному стані. У цьому прикладі змінні класу не знаходяться в інтуїтивному стані.

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

Вихід є

Constructing singleton instance of A
Setting num2 to 0
1
0

0

java не ініціалізує значення будь-якого статичного або нестатичного члена даних, поки воно не буде викликано, але воно не створює його.

так що тут, коли num1 і num2 будуть викликані в основному, тоді вони будуть ініціалізовані зі значеннями

num1 = 0 + 1; і

num2 = 0;

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.