blocks - protected field java
¿Por qué mis campos se inicializan como nulos o con el valor predeterminado de cero cuando los he declarado e inicializado en el constructor de mi clase? (4)
Hay dos partes para usar variables en java / c / c ++. Una es declarar la variable y la otra 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 al usarla:
int x;
int x = 7;
si la variable está en el mismo alcance, obtendrá un error de compilación; sin embargo, como está descubriendo, si la variable está en un ámbito diferente, enmascarará la primera declaración.
Se supone que esta es una pregunta y respuesta canónica para preguntas similares donde el problema es el resultado de la sombra .
He definido dos campos en mi clase, uno de tipo de referencia y otro 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 programa Java tienen names . Estos se utilizan para referirse a esas entidades en otras partes de un programa.
El lenguaje Java define un scope para cada nombre
El alcance de una declaración es la región del programa dentro de la cual se puede hacer referencia a la entidad declarada por la declaración utilizando un nombre simple, siempre que sea visible (§6.4.1).
En otras palabras, el alcance es un concepto de tiempo de compilación que determina dónde se puede usar un nombre para referirse a alguna entidad del programa.
El programa que ha publicado tiene múltiples declaraciones. Empecemos con
private String[] elements;
private int capacity;
Estas son declaraciones de field , también llamadas variables de instancia , es decir. Un tipo de miembro declarado en un cuerpo de clase . Los estados de la especificación del lenguaje Java
El alcance de una declaración de un miembro
m
declarado o heredado por una clase tipoC
(§8.1.6) es todo el cuerpo deC
, incluidas las declaraciones de tipo anidadas.
Esto significa que puede usar los
elements
nombres y la
capacity
dentro del cuerpo de
StringArray
para referirse a esos campos.
Las dos primeras declaraciones en tu cuerpo de constructor
public StringArray() {
int capacity = 10;
String[] elements;
elements = new String[capacity];
}
son en realidad declaraciones de declaración de variables locales
Una declaración de declaración de variable local declara uno o más nombres de variables locales.
Esas dos declaraciones introducen dos nuevos nombres en su programa.
Sucede que esos nombres son los mismos que los de sus campos ''.
En su ejemplo, la declaración de
capacity
variable local también contiene un inicializador que
inicializa esa variable local
, no el campo del mismo nombre.
Su campo llamado
capacity
se inicializa al
valor predeterminado
para su tipo, es decir.
el valor
0
.
El caso de los
elements
es un poco diferente.
La declaración de declaración 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 alcance
El alcance de una declaración de variable local en un bloque (§14.4) es el resto del bloque en el que aparece la declaración, comenzando con su propio inicializador e incluyendo otros declaradores a la derecha en la declaración de declaración de variable local.
El bloque, en este caso, es el cuerpo del constructor.
Pero el cuerpo del constructor es parte del cuerpo de
StringArray
, lo que significa que los nombres de campo también están dentro del alcance.
Entonces, ¿cómo determina Java a qué se refiere?
Java introduce el concepto de scope para desambiguar.
Algunas declaraciones pueden estar sombreadas en parte de su alcance por otra declaración 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 establece
Una declaración
d
de una variable local o parámetro de excepción llamadon
sombras , en todo el alcance ded
, (a) las declaraciones de cualquier otro campo llamadon
que esté dentro del alcance en el punto donde ocurred
, y (b) las declaraciones de cualquier otras variables llamadasn
que están dentro del alcance en el punto donde ocurred
pero no se declaran en la clase más interna en la que se declarad
.
Esto significa que la variable local denominada
elements
tiene prioridad sobre el campo denominado
elements
.
La expresion
elements = new String[capacity];
por lo tanto, inicializa 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 usa en sus respectivas declaraciones de
return
refieren a los campos ya que sus declaraciones son las únicas en alcance en ese punto particular del programa.
Como los campos se inicializaron a
0
y eran
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 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;
}
Estado de reglas de sombreado
Una declaración
d
de un campo o parámetro formal llamadon
sombras, en todo el alcance ded
, las declaraciones de cualquier otra variable llamadan
que esté en el alcance en el punto donde se produced
.
En el ejemplo anterior, la declaración de la
capacity
parámetro constructor sombrea la declaración de la variable de instancia también denominada
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
this
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
}
Hay 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.
Otra convención ampliamente aceptada es agregar algún prefijo (o sufijo, lo que prefiera) a los miembros de la clase para distinguirlos de las variables locales.
Por ejemplo, miembros de la clase con prefijo
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;
}
}
La mayoría de los IDE ya tienen soporte disponible para esta notación, a continuación es para Eclipse
int capacity = 10;
en su 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.
¿No te advirtió tu IDE de esta sombra?