Как модификатор static влияет на этот код?

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, но я не могу понять.

Может кто-нибудь мне это объяснить?

lirui
источник
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.
Thomas
2
Что меня все еще смущает, так это тот факт, что хотя num1 не имеет явной инициализации, он (неявно) инициализируется нулем. На самом деле не должно быть никакой разницы между явной и неявной инициализацией ...
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()конструктора.
Hans Z
31

Что staticозначает модификатор , когда применяется к декларации переменной является то , что переменная является переменной класса , а не переменная экземпляра. Другими словами ... есть только одна num1переменная и только одна num2переменная.

(Кроме того: статическая переменная похожа на глобальную переменную в некоторых других языках, за исключением того, что ее имя не отображается везде. Даже если оно объявлено как a 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.

  • создается первый A. num1 и num2 становятся 1 и 1
  • чем static int num1;ничего не делает
  • чем static int num2=0;это записывает 0 в num2
Leonidos
источник
9

Это связано с порядком статических инициализаторов. Статические выражения в классах оцениваются в порядке сверху вниз.

Первым вызывается конструктор A, который устанавливает num1и num2оба значения в 1:

static A obj = new A();

Затем,

static int num2=0;

вызывается и снова устанавливает num2 = 0.

Вот почему num11 и num20.

В качестве примечания, конструктор не должен изменять статические переменные, это очень плохой дизайн. Вместо этого попробуйте другой подход к реализации синглтона в 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): для типа byte значение по умолчанию равно нулю, то есть значение (байта) 0. Для типа short значение по умолчанию равно нулю, то есть значение (short) 0. Для типа int значение по умолчанию равно нулю, то есть 0. Для типа long значение по умолчанию равно нулю, то есть 0L. Для типа float значение по умолчанию - положительный ноль, то есть 0,0f. Для типа double значение по умолчанию - положительный ноль, то есть 0,0d. Для типа char значением по умолчанию является нулевой символ, то есть '\ u0000'. Для типа boolean значение по умолчанию - false. Для всех ссылочных типов (§4.3) значение по умолчанию - null.

StarPinkER
источник
2

Может быть, стоит подумать об этом таким образом.

Классы - это чертежи объектов.

Объекты могут иметь переменные при их создании.

Классы также могут иметь переменные. Они объявлены как статические. Таким образом, они устанавливаются для класса, а не для экземпляров объекта.

Вы можете иметь только один из любого класса в приложении, так что это своего рода глобальное хранилище специально для этого класса. Разумеется, к этим статическим переменным можно получить доступ и изменить их из любого места в вашем приложении (при условии, что они общедоступны).

Вот и пример класса «Dog», который использует статическую переменную для отслеживания количества созданных экземпляров.

Класс «Dog» - это облако, а оранжевые прямоугольники - это экземпляры «Dog».

Класс собак

читать далее

Надеюсь это поможет!

Если вам хочется мелочей, эту идею впервые предложил Платон.

Горан
источник
1

Ключевое слово static используется в java в основном для управления памятью. Мы можем применять ключевое слово static с переменными, методами, блоками и вложенным классом. Ключевое слово static принадлежит классу, а не экземпляру класса. Краткое объяснение ключевого слова static:

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
w25r
источник
0

java не инициализирует значение какого-либо статического или нестатического элемента данных, пока он не будет вызван, но создает его.

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

число1 = 0 + 1; и

num2 = 0;

дипак тивари
источник