No método ou no escopo da classe, a linha abaixo é compilada (com aviso):
int x = x = 1;
No escopo da classe, onde as variáveis obtêm seus valores padrão , o seguinte fornece o erro de 'referência indefinida':
int x = x + 1;
Não é o primeiro x = x = 1
deve terminar com o mesmo erro de 'referência indefinida'? Ou talvez a segunda linha int x = x + 1
deva compilar? Ou há algo que estou perdendo?
java
compiler-construction
Marcin
fonte
fonte
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.static int x = x + 1
falha em Java.int a = this.a + 1;
eint b = 1; int a = b + 1;
no escopo da classe (ambos ok em Java) falham, provavelmente devido a §17.4.5.2 - "Um inicializador de variável para um campo de instância não pode fazer referência à instância que está sendo criada." Não sei se é explicitamente permitido em algum lugar, mas estático não tem essa restrição. Em Java as regras são diferentes estatic int x = x + 1
não pela mesma razão queint x = x + 1
fazRespostas:
tl; dr
Para campos ,
int b = b + 1
é ilegal porqueb
é uma referência de encaminhamento ilegal parab
. Você pode realmente consertar isso escrevendoint b = this.b + 1
, que compila sem reclamações.Para variáveis locais ,
int d = d + 1
é ilegal porqued
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:
A declaração de
b
é inválida e falha com umillegal forward reference
erro.A declaração de
d
é inválida e falha com umvariable 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:
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.§8.3.2.3 diz:
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
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ênciaa
está sendo atribuída, portanto, é legal referir-sea
antes daa
declaração completa de.int b = this.b + 1
também compila porque viola (c): a referênciathis.b
não é um nome simples (é qualificada comthis.
). Essa estranha construção ainda está perfeitamente bem definida, poisthis.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 finalb
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:
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 é:
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 ad
é resolvido para a variável local fine, mas comod
não foi atribuído antes ded
ser acessado, o compilador emite um erro. Emint c = c = 1
,c = 1
acontece primeiro, que atribuic
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 campoint b = (b = 1) + b
), porqued
é definitivamente atribuída no momento em que o finald
é alcançado.fonte
int b = b + 1
b está à direita (não à esquerda) da atribuição, portanto, violaria isso ...int x = x = 1
, na qual caso nada disso se aplicaria.é equivalente a
enquanto em
primeiro precisamos calcular,
x+1
mas o valor de x não é conhecido, então você obtém um erro (o compilador sabe que o valor de x não é conhecido)fonte
int x = x = 1;
é equivalente aint x = (x = 1)
, nãox = 1; x = x;
. Você não deve receber um aviso do compilador por fazer isso.int x = x = 1;
s equivalente a intx = (x = 1)
por causa da associatividade à direita do=
operadorint x = (x = 1)
é equivalente aint x; x = 1; x = x;
(declaração da variável, avaliação do inicializador de campo, atribuição da variável ao resultado da referida avaliação), daí o avisoÉ aproximadamente equivalente a:
Em primeiro lugar,
int <var> = <expression>;
é sempre equivalente aNesse caso, sua expressão é
x = 1
, que também é uma afirmação.x = 1
é uma declaração válida, uma vez que o varx
já foi declarado. É também uma expressão com o valor 1, ao qual é atribuídox
novamente.fonte
0
valor padrão para ints, então eu esperaria que o resultado fosse 1, não oundefined reference
.x + 1
não tem valor definido, poisx
não foi inicializado.x
é definido como uma variável de membro ("no escopo da classe").Em java ou em qualquer linguagem moderna, a atribuição vem da direita.
Suponha que você tenha duas variáveis x e y,
Esta declaração é válida e é assim que o compilador os divide.
Mas no seu caso
O compilador deu uma exceção porque, ele se divide assim.
fonte
int x = x = 1;
não é igual a:javap nos ajuda novamente, estas são instruções JVM geradas para este código:
mais como:
Não há razão para lançar um erro de referência indefinida. Agora há uso de variável antes de sua inicialização, portanto, este código está em total conformidade com a especificação. Na verdade, não há nenhum uso de variável , apenas atribuições. E o compilador JIT irá ainda mais longe, eliminará tais construções. Sinceramente, não entendo como esse código está conectado à especificação JLS de inicialização e uso de variáveis. Sem uso, sem problemas. ;)
Por favor, corrija se eu estiver errado. Não consigo entender por que outras respostas, que se referem a muitos parágrafos do JLS, reúnem tantos pontos positivos. Esses parágrafos não têm nada em comum com este caso. Apenas duas atribuições em série e nada mais.
Se escrevermos:
é igual a:
A expressão mais à direita é apenas atribuída a variáveis uma a uma, sem qualquer recursão. Podemos bagunçar as variáveis da maneira que quisermos:
fonte
Em
int x = x + 1;
você adiciona 1 ax, então qual é o valor dex
, ainda não foi criado.Mas o in
int x=x=1;
irá compilar sem nenhum erro porque você atribui 1 ax
.fonte
Seu primeiro trecho de código contém um segundo em
=
vez de um sinal de adição. Isso será compilado em qualquer lugar, enquanto a segunda parte do código não será compilada em nenhum deles.fonte
Na segunda parte do código, x é usado antes de sua declaração, enquanto na primeira parte do código ele é apenas atribuído duas vezes, o que não faz sentido, mas é válido.
fonte
Vamos decompô-lo passo a passo, associativo correto
x = 1
, atribua 1 a uma variável xint x = x
, atribua o que x é para si mesmo, como um int. Como x foi atribuído anteriormente como 1, ele retém 1, embora de maneira redundante.Isso compila bem.
x + 1
, adicione um a uma variável x. No entanto, sendo x indefinido, ocorrerá um erro de compilação.int x = x + 1
, portanto, esta linha compila erros como a parte direita de igual não compilará adicionando um a uma variável não atribuídafonte
=
operadores, então é o mesmo queint x = (x = 1);
.O segundo
int x=x=1
é compilar porque você está atribuindo o valor ax, mas em outro casoint x=x+1
aqui a variável x não é inicializada. Lembre-se de que as variáveis locais java não são inicializadas com o valor padrão. Nota Se for (int x=x+1
) também estiver no escopo da classe, haverá um erro de compilação já que a variável não foi criada.fonte
compila com sucesso no Visual Studio 2008 com aviso
fonte
c
vez de,java
mas aparentemente era a outra pergunta.bool y;
ey==true
retornará false.void main() { int x = x + 1; printf("%d ", x); }
no Visual Studio 2008, em Debug eu obtenho a exceçãoRun-Time Check Failure #3 - The variable 'x' is being used without being initialized.
e em Release eu recebo o número1896199921
impresso no console.static
campo (variável estática de nível de classe), as mesmas regras se aplicam. Por exemplo, um campo declarado comopublic static int x = x + 1;
compila sem aviso no Visual C #. Possivelmente o mesmo em Java?x não é inicializado em
x = x + 1
;.A linguagem de programação Java é tipificada estaticamente, o que significa que todas as variáveis devem primeiro ser declaradas antes de serem usadas.
Veja os tipos de dados primitivos
fonte
A linha de código não é compilada com um aviso devido ao modo como o código realmente funciona. Quando você executa o código
int x = x = 1
, Java primeiro cria a variávelx
, conforme definido. Em seguida , executa o código de atribuição (x = 1
). Comox
já está definido, o sistema não tem erros configuradosx
para 1. Isso retorna o valor 1, porque agora é o valor dex
. Portanto,x
agora está finalmente definido como 1.Java basicamente executa o código como se fosse este:
No entanto, em sua segunda parte do código,
int x = x + 1
a+ 1
instrução precisax
ser definida, o que até então não é. Como as instruções de atribuição sempre significam que o código à direita do=
é executado primeiro, o código falhará porquex
é indefinido. Java executaria o código assim:fonte
O compilador leu as declarações da direita para a esquerda e planejamos fazer o oposto. É por isso que me incomodou no início. Faça disso um hábito de ler declarações (código) da direita para a esquerda, você não terá esse problema.
fonte