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 d
nã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 reference
erro.
A declaração de d
é inválida e falha com um variable d might not have been initialized
erro.
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
m
declarado 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 a
antes da a
declaração completa de.
int b = this.b + 1
também compila porque viola (c): a referência this.b
não é um nome simples (é qualificada com this.
). Essa estranha construção ainda está perfeitamente bem definida, pois this.b
tem o valor zero.
Portanto, basicamente, as restrições nas referências de campo nos inicializadores evitam que int a = a + 1
sejam compilados com sucesso.
Observe que a declaração de campo int b = (b = 1) + b
irá falhar para compilar, porque a final b
ainda é 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
, x
deve 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 d
não foi atribuído antes de d
ser acessado, o compilador emite um erro. Em int c = c = 1
, c = 1
acontece primeiro, que atribui c
e, 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.
static
na 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.