tl; dr
Para campos , int b = b + 1é ilegal porque bé uma referência de encaminhamento ilegal para b. Você pode realmente consertar isso escrevendo int b = this.b + 1, que compila sem reclamações.
Para variáveis locais , int d = d + 1é ilegal porque dnão é inicializado antes do uso. Este não é o caso dos campos, que são sempre inicializados por padrão.
Você pode ver a diferença ao tentar compilar
int x = (x = 1) + x;
como uma declaração de campo e como uma declaração de variável local. O primeiro falhará, mas o último terá sucesso, por causa da diferença na semântica.
Introdução
Em primeiro lugar, as regras para inicializadores de campo e variável local são muito diferentes. Portanto, esta resposta abordará as regras em duas partes.
Usaremos este programa de teste em:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
A declaração de bé inválida e falha com um illegal forward referenceerro.
A declaração de dé inválida e falha com um variable d might not have been initializederro.
O fato de esses erros serem diferentes deve indicar que as razões para os erros também são diferentes.
Campos
Os inicializadores de campo em Java são governados por JLS §8.3.2 , Inicialização de campos.
O escopo de um campo é definido em JLS §6.3 , Escopo de uma declaração.
As regras relevantes são:
- O escopo de uma declaração de um membro
mdeclarado em ou herdado por um tipo de classe C (§8.1.6) é o corpo inteiro de C, incluindo quaisquer declarações de tipo aninhado.
- As expressões de inicialização para variáveis de instância podem usar o nome simples de qualquer variável estática declarada na classe ou herdada pela classe, mesmo uma cuja declaração ocorra textualmente mais tarde.
- O uso de variáveis de instância cujas declarações aparecem textualmente após o uso às vezes é restrito, mesmo que essas variáveis de instância estejam no escopo. Consulte §8.3.2.3 para as regras precisas que regem a referência direta a variáveis de instância.
§8.3.2.3 diz:
A declaração de um membro precisa aparecer textualmente antes de ser usada apenas se o membro for um campo de instância (respectivamente estático) de uma classe ou interface C e todas as seguintes condições forem válidas:
- O uso ocorre em um inicializador de variável de instância (respectivamente estático) de C ou em um inicializador de instância (respectivamente estático) de C.
- O uso não está no lado esquerdo de uma atribuição.
- O uso é feito por meio de um nome simples.
- C é a classe ou interface mais interna que envolve o uso.
Na verdade, você pode consultar os campos antes de serem declarados, exceto em certos casos. Essas restrições têm como objetivo evitar códigos como
int j = i;
int i = j;
da compilação. A especificação Java diz que "as restrições acima são projetadas para capturar, em tempo de compilação, inicializações circulares ou malformadas".
O que essas regras realmente se resumem?
Em suma, as regras basicamente dizem que você deve declarar um campo antes de uma referência a esse campo se (a) a referência estiver em um inicializador, (b) a referência não estiver sendo atribuída, (c) a referência for um nome simples (sem qualificadores como this.) e (d) não está sendo acessado de dentro de uma classe interna. Portanto, uma referência direta que satisfaça todas as quatro condições é ilegal, mas uma referência direta que falhe em pelo menos uma condição está OK.
int a = a = 1;compila porque viola (b): a referência a está sendo atribuída, portanto, é legal referir-se aantes da adeclaração completa de.
int b = this.b + 1também compila porque viola (c): a referência this.bnão é um nome simples (é qualificada com this.). Essa estranha construção ainda está perfeitamente bem definida, pois this.btem o valor zero.
Portanto, basicamente, as restrições nas referências de campo nos inicializadores evitam que int a = a + 1sejam compilados com sucesso.
Observe que a declaração de campo int b = (b = 1) + birá falhar para compilar, porque a final bainda é uma referência para a frente ilegal.
Variáveis locais
As declarações de variáveis locais são regidas por JLS §14.4 , Declarações de declaração de variáveis locais.
O escopo de uma variável local é definido em JLS §6.3 , Escopo de uma declaração:
- 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 declaradores adicionais à direita na declaração de declaração de variável local.
Observe que os inicializadores estão dentro do escopo da variável sendo declarada. Então, por que não int d = d + 1;compila?
O motivo é devido à regra de Java sobre atribuição definitiva ( JLS §16 ). A atribuição definida basicamente diz que todo acesso a uma variável local deve ter uma atribuição anterior a essa variável, e o compilador Java verifica os loops e ramificações para garantir que a atribuição sempre ocorra antes de qualquer uso (é por isso que a atribuição definida tem uma seção de especificação inteira dedicada para ele). A regra básica é:
- Para cada acesso de uma variável local ou campo final em branco
x, xdeve ser definitivamente atribuído antes do acesso, ou ocorrerá um erro em tempo de compilação.
Em int d = d + 1;, o acesso a dé resolvido para a variável local fine, mas como dnão foi atribuído antes de dser acessado, o compilador emite um erro. Em int c = c = 1, c = 1acontece primeiro, que atribui ce, em seguida, cé inicializado com o resultado dessa atribuição (que é 1).
Observe que por causa das regras de atribuição definidas, a declaração da variável local int d = (d = 1) + d; será compilada com sucesso ( ao contrário da declaração do campo int b = (b = 1) + b), porque dé definitivamente atribuída no momento em que o final dé alcançado.
staticna variável de escopo de classe, como emstatic int x = x + 1;, obterá o mesmo erro? Porque em C # faz diferença se é estático ou não estático.