Почему мои поля инициализируются нулевым или нулевым значением по умолчанию, когда я объявил и инициализировал их в конструкторе моего класса?
это должен быть канонический вопрос и ответ на аналогичные вопросы, где проблема является результатом слежка.
Я определил два поля в своем классе, одно из ссылочного типа и одно из примитивного типа. В конструкторе класса я пытаюсь инициализировать их некоторыми пользовательскими значениями.
когда я позже запрашиваю значения этих полей, они возвращаются со значениями по умолчанию 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()
для возврата правильно инициализированного экземпляра массива.
4 ответов
объекты (пакеты, типы, методы, переменные и т. д.), определенные в Java-программы имена. Они используются для ссылки на эти объекты в других частях программы.
язык Java определяет область для каждого
на область декларации является областью программы, в рамках которой на сущность, объявленную декларацией, можно ссылаться с помощью простое имя, при условии, что он виден (§6.4.1).
другими словами, область - это концепция времени компиляции, которая определяет, где имя может использоваться для ссылки на некоторую сущность программы.
программа, которую вы опубликовали, имеет несколько объявлений. Начнем с
private String[] elements;
private int capacity;
это поле заявления, также называемый переменные, ie. тип члена, объявленный в класс тело. В спецификации языка Java
объем заявления члена
m
объявлено или унаследовано a тип классаC
(§8.1.6) - это все телоC
, включая все вложенные объявление типа.
это означает, что вы можете использовать имена elements
и capacity
в теле StringArray
для ссылки на эти поля.
два первых оператора в вашем конструкторе тело
public StringArray() {
int capacity = 10;
String[] elements;
elements = new String[capacity];
}
на самом деле операторы объявления локальных переменных
A локальная переменная оператор объявления объявляет одно или несколько имен локальных переменных.
эти два утверждения вводят два новых имени в вашу программу. Просто так получилось, что эти имена совпадают с именами ваших полей. В вашем примере, объявление локальной переменной для capacity
также содержит инициализатор, который инициализирует эту локальную переменную, а не поле с тем же именем. Поле с именем capacity
инициализируется значение по умолчанию для его типа, т. е. значение 0
.
в случае elements
немного отличается. Оператор объявления локальной переменной вводит новое имя, но как насчет выражение задание?
elements = new String[capacity];
что такое сущность elements
имеете в виду?
правила область государство
область локальная переменная объявление в блоке (§14.4) является остальная часть блока, в котором появляется объявление, начиная с собственный инициализатор и включение любых дополнительных деклараторов справа в оператор объявления локальной переменной.
блок, в данном случае, является телом конструктора. Но тело конструктора является частью тела StringArray
, что означает, что имена полей также в объеме. Итак, как Java определяет, что вы имеете в виду?
Java вводит понятие слежка для устранения неоднозначности.
некоторые объявления могут быть затемнены в части их области другим объявление того же имени, в этом случае простое имя не может быть используется для ссылки на объявленный объект.
(a простое имя быть одним идентификатором, например. elements
.)
в документации также говорится,
декларация
d
на локальная переменная или параметр исключения с именемn
тени, во всем объемеd
, (a) объявления любого другого поля с именемn
, находящиеся в области, в точке, гдеd
происходит, и (b) объявления любых других переменных с именем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
shadows объявление переменной экземпляра также называется 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
}
здесь слежка правила для каждого тип переменной, метод и тип.
моя рекомендация заключается в том, что вы используете уникальные имена, где это возможно, чтобы избежать поведение в целом.
int capacity = 10;
в вашем конструкторе объявляется локальная переменная capacity
, который тени поле класса.
средство состоит в том, чтобы отбросить int
:
capacity = 10;
это изменит значение поля. То же самое для другого поля в классе.
разве ваша IDE не предупреждала вас об этом затенении?
другое широко распространенное соглашение - иметь некоторый префикс (или суффикс - все, что вы предпочитаете), добавленный к членам класса, чтобы отличить их от локальных переменных.
например члены класса с 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;
}
}
Большинство IDEs уже имеют доступную поддержку для этой нотации, ниже для Eclipse
есть две части для использования переменных в java/c / C++. Один из них-объявить переменную, а другой-использовать переменную (будь то присвоение значения или использование его в вычислении).
когда вы объявляете переменную, вы должны объявить его тип. Таким образом, вы будете использовать
int x; // to declare the variable
x = 7; // to set its value
вам не нужно повторно объявлять переменную при ее использовании:
int x;
int x = 7;
если переменная в той же области вы получите ошибку компилятора, однако, как вы выяснить, если переменная находится в другой области, вы будете маскировать первое объявление.