Вопрос-ответ

Why are my fields initialized to null or to the default value of zero when I've declared and initialized them in my class' constructor?

Почему мои поля инициализируются нулем или значением по умолчанию, равным нулю, когда я объявил и инициализировал их в конструкторе моего класса'?

Это должно быть каноническим вопросом и ответом на аналогичные вопросы, где проблема является результатом затенения.


Я определил в своем классе два поля, одно ссылочного типа и одно примитивного типа. В конструкторе класса' я пытаюсь инициализировать их некоторыми пользовательскими значениями.

Когда я позже запрашиваю значения этих полей, они возвращаются со значениями Java по умолчанию для них, null для ссылочного типа и 0 для примитивного типа. Почему это происходит?

Вот воспроизводимый пример:

public class Sample {
public static void main(String[] args) throws Exception {
StringArray array = new StringArray();
System.out.println(array.getCapacity()); // prints 0
System.out.println(array.getElements()); // prints null
}
}

class StringArray {
private String[] elements;
private int capacity;
public StringArray() {
int capacity = 10;
String[] elements;
elements = new String[capacity];
}
public int getCapacity() {
return capacity;
}
public String[] getElements() {
return elements;
}
}

Я ожидал, что getCapacity() вернет значение 10 и getElements() вернет правильно инициализированный экземпляр массива.

Переведено автоматически
Ответ 1

Сущности (пакеты, типы, методы, переменные и т.д.), определенные в программе Java, имеют имена. Они используются для обозначения этих сущностей в других частях программы.

Язык Java определяет область для каждого имени


Область объявления - это область программы, в пределах которой на объект, объявленный объявлением, можно ссылаться с помощью простого имени, при условии, что он виден (§ 6.4.1).


Другими словами, scope - это концепция времени компиляции, которая определяет, где имя может использоваться для обозначения некоторой программной сущности.

Опубликованная вами программа имеет несколько объявлений. Давайте начнем с

private String[] elements;
private int capacity;

Это объявления полей, также называемые переменными экземпляра, т.Е. тип члена, объявленный в теле класса. Спецификация языка Java гласит


Область действия объявления члена, m объявленного в типе класса C или унаследованного им C (§ 8.1.6), - это все тело класса, , включая любые объявления вложенных типов.


Это означает, что вы можете использовать имена elements и capacity в теле StringArray для ссылки на эти поля.

Два первых оператора в теле вашего конструктора

public StringArray() {
int capacity = 10;
String[] elements;
elements = new String[capacity];
}

на самом деле это операторы объявления локальной переменной


Оператор объявления локальной переменной объявляет одно или несколько имен локальной переменной.


Эти два оператора вводят два новых имени в вашу программу. Просто так получилось, что эти имена совпадают с вашими полями'. В вашем примере объявление локальной переменной для capacity также содержит инициализатор, который инициализирует эту локальную переменную, а не поле с тем же именем. Ваше поле с именем capacity инициализируется значением по умолчанию для своего типа, т.Е.. значение 0.

Случай с elements немного отличается. Оператор объявления локальной переменной вводит новое имя, но как насчет выражения присваивания?

elements = new String[capacity];

На какую сущность elements ссылается?

Правила области гласят


Область действия объявления локальной переменной в блоке (§ 14.4) - это остальная часть блока, в котором появляется объявление, начиная с его собственного инициализатора и включая любые дополнительные деклараторы справа в инструкции объявления локальной переменной.


Блок в данном случае является телом конструктора. Но тело конструктора является частью тела StringArray, что означает, что имена полей также находятся в области видимости. Итак, как Java определяет, на что вы ссылаетесь?

Java вводит концепцию затенения для устранения неоднозначности.


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


(простое имя является единственным идентификатором, например. elements.)

В документации также указано


Объявление d локальной переменной или параметра исключения с именем n
shadows во всей области d, (а) объявления любых других полей с именем n, которые находятся в области видимости в точке, где d встречается, и (б) объявления любых других переменных с именем n, которые находятся в области видимости в точке, где d встречается, но не объявлены во внутреннем классе, в котором d объявлен.


Это означает, что локальная переменная с именем elements имеет приоритет над полем с именем elements. Выражение

elements = new String[capacity];

следовательно, инициализируется локальная переменная, а не поле. Поле инициализируется значением по умолчанию для своего типа, т.Е.. значение null.

Внутри ваших методов getCapacity и getElements имена, которые вы используете в их соответствующих return операторах, относятся к полям, поскольку их объявления являются единственными в области видимости на данном конкретном этапе программы. Поскольку поля были инициализированы 0 и null, это возвращаемые значения.

Решение состоит в том, чтобы полностью избавиться от объявлений локальных переменных и, следовательно, сделать так, чтобы имена ссылались на переменные экземпляра, как вы изначально хотели. Например

public StringArray() {
capacity = 10;
elements = new String[capacity];
}

Затенение параметрами конструктора

Аналогично ситуации, описанной выше, у вас могут быть формальные параметры (конструктора или метода), затеняющие поля с таким же именем. Например

public StringArray(int capacity) {
capacity = 10;
}

Состояние правил затенения


Объявление d поля или формального параметра с именем n затеняет во всей области видимости d объявления любых других переменных с именем n, которые находятся в области видимости в точке, где происходит d.


В приведенном выше примере объявление параметра конструктора capacity затеняет объявление переменной экземпляра с таким же именем capacity. Поэтому невозможно ссылаться на переменную экземпляра с ее простым именем. В таких случаях нам нужно ссылаться на него с его полным именем.


Полное имя состоит из имени, токена "." и идентификатора.


В этом случае мы можем использовать основное выражение this как часть выражения доступа к полю для ссылки на переменную экземпляра. Например

public StringArray(int capacity) {
this.capacity = 10; // to initialize the field with the value 10
// or
this.capacity = capacity; // to initialize the field with the value of the constructor argument
}

Существуют правила затенения для каждого вида переменной, метода и типа.

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

Ответ 2

int capacity = 10; в вашем конструкторе объявляется локальная переменная, capacity которая затеняет поле класса.

Решение состоит в том, чтобы удалить int:

capacity = 10;

Это изменит значение поля. То же самое для другого поля в классе.

Разве ваша IDE не предупреждала вас об этом затенении?

Ответ 3

Использование переменных в java / c / c ++ состоит из двух частей. Одна заключается в объявлении переменной, а другая - в использовании переменной (будь то присвоение значения или использование его в вычислении).

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

int x;   // to declare the variable
x = 7; // to set its value

Вам не нужно повторно объявлять переменную при ее использовании:

int x;
int x = 7;

если переменная находится в той же области видимости, вы получите ошибку компилятора; однако, как вы выясняете, если переменная находится в другой области видимости, вы замаскируете первое объявление.

Ответ 4

Другим широко принятым соглашением является добавление некоторого префикса (или суффикса - как вам больше нравится) к членам класса, чтобы отличать их от локальных переменных.

Например, члены класса с префиксом m_:

class StringArray {
private String[] m_elements;
private int m_capacity;

public StringArray(int capacity) {
m_capacity = capacity;
m_elements = new String[capacity];
}

public int getCapacity() {
return m_capacity;
}

public String[] getElements() {
return m_elements;
}
}



Большинство IDE уже имеют доступную поддержку этой нотации, ниже для Eclipse

введите описание изображения здесь

java