Por que meus campos foram inicializados para null ou para o valor padrão zero quando os declarei e inicializei no construtor da minha class?

Esta é uma questão canônica e uma resposta para questões semelhantes, nas quais a questão é resultado do sombreamento .


Eu defini dois campos na minha class, um de um tipo de referência e um de um tipo primitivo. No construtor da class, tento inicializá-los para alguns valores personalizados.

Quando eu mais tarde consultar os valores desses campos, eles retornam com os valores padrão do Java para eles, null para o tipo de referência e 0 para o tipo primitivo. Por que isso está acontecendo?

Aqui está um exemplo reproduzível:

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

Eu esperava que getCapacity() retornasse o valor 10 e getElements() para retornar uma instância de array devidamente inicializada.

Entidades (pacotes, tipos, methods, variables, etc.) definidas em um programa Java possuem nomes . Estes são usados ​​para se referir àquelas entidades em outras partes de um programa.

A linguagem Java define um escopo para cada nome

O escopo de uma declaração é a região do programa dentro da qual a entidade declarada pela declaração pode ser referida usando um nome simples, desde que seja visível (§6.4.1).

Em outras palavras, o escopo é um conceito de tempo de compilation que determina onde um nome pode ser usado para se referir a alguma entidade do programa.

O programa que você postou tem várias declarações. Vamos começar com

 private String[] elements; private int capacity; 

Estas são declarações de campo , também chamadas de variables ​​de instância , ie. um tipo de membro declarado em um corpo de class . Os estados da especificação da linguagem Java

O escopo de uma declaração de um membro m declarado ou herdado por um tipo de class C (§8.1.6) é o corpo inteiro de C , incluindo quaisquer declarações de tipo aninhadas.

Isso significa que você pode usar os elements nomes e capacity no corpo de StringArray para se referir a esses campos.

As duas primeiras declarações no corpo do seu construtor

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

são, na verdade , declarações de declaração de variável local

Uma declaração de declaração de variável local declara um ou mais nomes de variables ​​locais.

Essas duas instruções introduzem dois novos nomes em seu programa. Acontece que esses nomes são iguais aos seus campos. Em seu exemplo, a declaração de variável local para capacity também contém um inicializador que inicializa essa variável local , não o campo com o mesmo nome. Seu campo denominado capacity é inicializado com o valor padrão para seu tipo, isto é. o valor 0 .

O caso dos elements é um pouco diferente. A declaração de declaração de variável local introduz um novo nome, mas e a expressão de atribuição ?

 elements = new String[capacity]; 

Qual entidade são elements referentes a?

As regras do estado do escopo

O escopo de uma declaração de variável local em um bloco (§14.4) é o resto do bloco no qual a declaração aparece, começando com seu próprio inicializador e incluindo quaisquer outros declaradores à direita na declaração de declaração de variável local.

O bloco, nesse caso, é o corpo do construtor. Mas o corpo do construtor é parte do corpo de StringArray , o que significa que os nomes de campo também estão no escopo. Então, como o Java determina o que você está se referindo?

Java introduz o conceito de Shadowing para desambiguar.

Algumas declarações podem ser sombreadas em parte de seu escopo por outra declaração com o mesmo nome, caso em que um nome simples não pode ser usado para se referir à entidade declarada.

(um nome simples é um identificador único, por exemplo, elements .)

A documentação também afirma

Uma declaração d de uma variável local ou parâmetro de exceção chamado n sombras , em todo o escopo de d , (a) as declarações de quaisquer outros campos nomeados n que estão no escopo no ponto onde d ocorre , e (b) as declarações de qualquer outras variables ​​nomeadas n que estão no escopo no ponto onde d ocorre, mas não são declaradas na class mais interna em que d é declarado.

Isso significa que a variável local denominada elements tem prioridade sobre o campo denominado elements . A expressão

 elements = new String[capacity]; 

Portanto, está inicializando a variável local, não o campo. O campo é inicializado com o valor padrão para seu tipo, isto é. o valor null .

Dentro de seus methods getCapacity e getElements , os nomes que você usa em suas respectivas declarações de return referem aos campos, pois suas declarações são as únicas no escopo naquele ponto específico do programa. Como os campos foram inicializados para 0 e null , esses são os valores retornados.

A solução é livrar-se completamente das declarações de variables ​​locais e, portanto, os nomes se referem às variables ​​de instância, como você queria originalmente. Por exemplo

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

Sombreamento com parâmetros de construtor

Semelhante à situação descrita acima, você pode ter parâmetros formais (construtor ou método) sombreando campos com o mesmo nome. Por exemplo

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

Estado das regras de sombreamento

Uma declaração d de um campo ou parâmetro formal chamado n sombras, em todo o escopo de d , as declarações de quaisquer outras variables ​​nomeadas n que estão no escopo no ponto onde d ocorre.

No exemplo acima, a declaração da capacity parâmetro do construtor faz sombra à declaração da variável da instância também denominada capacity . Portanto, é impossível referir-se à variável de instância com seu nome simples. Em tais casos, precisamos nos referir a ele com seu nome qualificado .

Um nome qualificado consiste em um nome, um “.” token e um identificador.

Nesse caso, podemos usar a expressão primária como parte de uma expressão de access de campo para se referir à variável de instância. Por exemplo

 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 } 

Existem regras de sombreamento para cada tipo de variável , método e tipo.

Minha recomendação é que você use nomes exclusivos sempre que possível para evitar o comportamento completamente.

int capacity = 10; em seu construtor está declarando uma capacity variável local que sombras o campo da class.

O remédio é abandonar o int :

capacity = 10;

Isso mudará o valor do campo. Idem para o outro campo da turma.

O seu IDE não avisou você sobre esse sombreamento?

Outra convenção amplamente aceita é ter algum prefixo (ou sufixo – o que você preferir) adicionado aos membros da class para distingui-los das variables ​​locais.

Por exemplo, membros da class com o prefixo 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; } } 

A maioria dos IDEs já tem suporte disponível para essa notação, abaixo é para o Eclipse

insira a descrição da imagem aqui

Existem duas partes para usar variables ​​em java / c / c ++. Uma é declarar a variável e a outra é usar a variável (seja atribuindo um valor ou usando-o em um cálculo).

Quando você declara uma variável, você deve declarar seu tipo. Então você usaria

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

Você não precisa declarar novamente uma variável ao usá-la:

 int x; int x = 7; 

se a variável estiver no mesmo escopo, você receberá um erro do compilador; no entanto, como você está descobrindo, se a variável estiver em um escopo diferente, você irá mascarar a primeira declaração.

Intereting Posts