¿Por qué mis campos se inicializaron a nulo o al valor predeterminado de cero cuando los he declarado y los inicialicé en el constructor de mi clase?

Esta debe ser una pregunta y respuesta canónica para preguntas similares donde el problema es el resultado de un seguimiento .


He definido dos campos en mi clase, uno de tipo de referencia y uno de tipo primitivo. En el constructor de la clase, trato de inicializarlos a algunos valores personalizados.

Cuando más tarde consulto los valores de esos campos, vuelven con los valores predeterminados de Java para ellos, null para el tipo de referencia y 0 para el tipo primitivo. ¿Por qué está pasando esto?

Aquí hay un ejemplo reproducible:

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

Esperaba que getCapacity() devolviera el valor 10 y getElements() para devolver una instancia de matriz inicializada correctamente.

Las entidades (paquetes, tipos, métodos, variables, etc.) definidas en un progtwig Java tienen nombres . Estos se usan para referirse a esas entidades en otras partes de un progtwig.

El lenguaje Java define un scope para cada nombre

El scope de una statement es la región del progtwig dentro del cual se puede hacer referencia a la entidad declarada por la statement utilizando un nombre simple, siempre que sea visible (§6.4.1).

En otras palabras, el scope es un concepto de tiempo de comstackción que determina dónde se puede usar un nombre para referirse a alguna entidad de progtwig.

El progtwig que has publicado tiene múltiples declaraciones. Empecemos con

 private String[] elements; private int capacity; 

Estas son declaraciones de campo , también llamadas variables de instancia , es decir. un tipo de miembro declarado en un cuerpo de clase . Los estados de especificación de lenguaje Java

El scope de una statement de un miembro m declarado o heredado por un tipo de clase C (§8.1.6) es el cuerpo completo de C , incluidas las declaraciones de tipos nesteds.

Esto significa que puede usar los elements los nombres y la capacity dentro del cuerpo de StringArray para referirse a esos campos.

Las dos primeras declaraciones en tu cuerpo constructor

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

en realidad son declaraciones de declaraciones de variables locales

Una statement de statement de variable local declara uno o más nombres de variables locales.

Esas dos declaraciones introducen dos nombres nuevos en su progtwig. Da la casualidad de que esos nombres son los mismos que sus campos ‘. En su ejemplo, la statement de la variable local para la capacity también contiene un inicializador que inicializa esa variable local , no el campo del mismo nombre. La capacity su campo se inicializa al valor predeterminado para su tipo, es decir. el valor 0 .

El caso de los elements es un poco diferente. La statement de statement de variable local introduce un nuevo nombre, pero ¿qué pasa con la expresión de asignación ?

 elements = new String[capacity]; 

¿A qué entidad se refieren los elements ?

Las reglas del estado del scope

El scope de una statement de variable local en un bloque (§14.4) es el rest del bloque en el que aparece la statement, comenzando con su propio inicializador e incluyendo cualquier otro declarante a la derecha en la statement de statement de variable local.

El bloque, en este caso, es el cuerpo constructor. Pero el cuerpo constructor es parte del cuerpo de StringArray , lo que significa que los nombres de los campos también están dentro del scope. Entonces, ¿cómo determina Java a qué te refieres?

Java introduce el concepto de Sombreado para desambiguar.

Algunas declaraciones pueden aparecer sombreadas en parte de su scope por otra statement del mismo nombre, en cuyo caso no se puede usar un nombre simple para referirse a la entidad declarada.

(un nombre simple es un identificador único, por ejemplo, elements ).

La documentación también indica

Una statement d de una variable local o parámetro de excepción denominado n sombras , en todo el scope de d , (a) las declaraciones de cualquier otro campo llamado n que estén en el scope en el punto donde ocurre d , y (b) las declaraciones de cualquier otras variables llamadas n que están en el scope en el punto donde ocurre d pero no están declaradas en la clase más interna en la que se declara d .

Esto significa que los elements nombrados de la variable local tienen prioridad sobre los elements nombrados en el campo. La expresion

 elements = new String[capacity]; 

por lo tanto, está inicializando la variable local, no el campo. El campo se inicializa al valor predeterminado para su tipo, es decir. el valor null .

Dentro de sus métodos getCapacity y getElements , los nombres que utiliza en sus respectivas declaraciones de return refieren a los campos ya que sus declaraciones son las únicas en el scope en ese punto particular del progtwig. Dado que los campos se inicializaron en 0 y null , esos son los valores devueltos.

La solución es deshacerse por completo de las declaraciones de variables locales y, por lo tanto, hacer que los nombres se refieran a las variables de instancia, como originalmente se quería. Por ejemplo

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

Sombreado con parámetros de constructor

Similar a la situación descrita anteriormente, puede tener parámetros formales (constructor o método) sombreando campos con el mismo nombre. Por ejemplo

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

El estado de las reglas de sombreado

Una statement d de un campo o parámetro formal llamado n sombras, en todo el scope de d , las declaraciones de cualquier otra variable llamada n que están en el scope en el punto donde ocurre d .

En el ejemplo anterior, la statement de la capacity parámetro constructor sombrea la statement de la variable de instancia también llamada capacity . Por lo tanto, es imposible hacer referencia a la variable de instancia con su nombre simple. En tales casos, debemos referirnos a él con su nombre calificado .

Un nombre calificado consiste en un nombre, un “.” token y un identificador.

En este caso, podemos usar la expresión primaria como parte de una expresión de acceso de campo para referirnos a la variable de instancia. Por ejemplo

 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 } 

Existen reglas de sombreado para cada tipo de variable , método y tipo.

Mi recomendación es que use nombres únicos siempre que sea posible para evitar el comportamiento por completo.

int capacity = 10; en tu constructor está declarando una capacity variable local que sombrea el campo de la clase.

El remedio es soltar el int :

capacity = 10;

Esto cambiará el valor del campo. Lo mismo para el otro campo en la clase.

¿Tu IDE no te advirtió sobre este ocultamiento?

Otra convención ampliamente aceptada es tener un prefijo (o sufijo, lo que prefiera) agregado a los miembros de la clase para distinguirlos de las variables locales.

Por ejemplo, miembros de la clase con m_ prefix:

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

La mayoría de los IDE ya tienen soporte disponible para esta notación, a continuación se muestra para Eclipse

enter image description here

Hay dos partes para usar variables en java / c / c ++. Uno es declarar la variable y el otro es usar la variable (ya sea asignando un valor o usándolo en un cálculo).

Cuando declaras una variable debes declarar su tipo. Entonces usarías

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

No tiene que volver a declarar una variable cuando la usa:

 int x; int x = 7; 

si la variable está en el mismo ámbito, obtendrá un error de comstackción; sin embargo, como está descubriendo, si la variable tiene un scope diferente, enmascarará la primera statement.