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

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


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

когда я позже запрашиваю значения этих полей, они возвращаются со значениями по умолчанию 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

enter image description here


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

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

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

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

int x;
int x = 7;   

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