Eu tenho o seguinte código:
public class Tests {
public static void main(String[] args) throws Exception {
int x = 0;
while(x<3) {
x = x++;
System.out.println(x);
}
}
}
Sabemos que ele deveria ter escrito just x++
ou or x=x+1
, mas x = x++
deve primeiro atribuir x
a si mesmo e depois incrementá-lo. Por que x
continuar com 0
como valor?
--atualizar
Aqui está o bytecode:
public class Tests extends java.lang.Object{
public Tests();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_3
4: if_icmpge 22
7: iload_1
8: iinc 1, 1
11: istore_1
12: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
15: iload_1
16: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
19: goto 2
22: return
}
Vou ler sobre as instruções para tentar entender ...
x++
é pós-incremento;x=
é atribuição de resultado ; o resultado dex++
é o originalx
(e há um efeito colateral de incremento, mas isso não altera o resultado), portanto, isso pode ser interpretado como:var tmp = x; x++; x = tmp;
Respostas:
Nota : Originalmente, eu publiquei o código C # nesta resposta para fins de ilustração, pois o C # permite que você passe
int
parâmetros por referência com aref
palavra - chave. Decidi atualizá-lo com código Java legal real, usando a primeiraMutableInt
classe que encontrei no Google para aproximar o queref
é feito em c #. Não sei dizer se isso ajuda ou prejudica a resposta. Eu direi que pessoalmente não fiz tanto desenvolvimento em Java; então, pelo que sei, poderia haver muito mais maneiras idiomáticas para ilustrar esse ponto.Talvez se escrevermos um método para fazer o equivalente ao que
x++
isso tornará isso mais claro.Direita? Incremente o valor passado e retorne o valor original: essa é a definição do operador pós-incremento.
Agora, vamos ver como esse comportamento se desenrola no seu código de exemplo:
postIncrement(x)
Faz o que? Incrementosx
, sim. E então retorna o quex
estava antes do incremento . Esse valor de retorno é atribuído ax
.Portanto, a ordem dos valores atribuídos
x
é 0, depois 1 e 0.Isso pode ser mais claro ainda se reescrevermos o acima:
Sua fixação no fato de que, quando você substitui
x
no lado esquerdo da atribuição acima pory
", você pode ver que ela primeiro incrementa xe depois atribui a y" me parece confuso. Não é issox
que está sendo atribuídoy
; é o valor anteriormente atribuídox
. Realmente, injetary
torna as coisas não diferentes do cenário acima; nós simplesmente temos:Portanto, é claro:
x = x++
efetivamente não altera o valor de x. Sempre faz com que x tenha os valores x 0 , x 0 + 1 e x 0 novamente.Atualização : Aliás, para que você não duvide que
x
seja atribuído a 1 "entre" a operação de incremento e a atribuição no exemplo acima, juntei uma demonstração rápida para ilustrar que esse valor intermediário realmente "existe", embora nunca seja "visto" no thread em execução.A demo chama
x = x++;
em loop enquanto um thread separado imprime continuamente o valor dex
para o console.Abaixo está um trecho da saída do programa acima. Observe a ocorrência irregular de 1s e 0s.
fonte
Integer
classe, que faz parte da biblioteca padrão, e ainda tem o benefício de receber uma caixa automática de e paraint
quase de forma transparente.x
no seu último exemplo deve ser declaradovolatile
, caso contrário, é um comportamento indefinido e vendo1
s é implementação específica.++
pode ser feito antes ou depois da atribuição. Na prática, pode haver um compilador que faça o mesmo que Java, mas você não gostaria de apostar nele.x = x++
funciona da seguinte maneira:x++
. A avaliação dessa expressão produz um valor de expressão (que é o valor dex
antes do incremento) e incrementosx
.x
, substituindo o valor incrementado.Portanto, a sequência de eventos se parece com a seguinte (é um bytecode descompilado real, produzido por
javap -c
, com meus comentários):Para comparação
x = ++x
:fonte
iinc
incrementa uma variável, não incrementa o valor da pilha nem deixa um valor na pilha (diferente de quase todas as outras operações aritméticas). Você pode querer adicionar o código gerado por++x
para comparação.Isso acontece porque o valor de
x
não é incrementado.é equivalente a
Explicação:
Vejamos o código de bytes para esta operação. Considere uma classe de amostra:
Agora, executando o desmontador de classe, obtemos:
Agora a Java VM é baseada em pilha, o que significa que, para cada operação, os dados serão enviados para a pilha e, a partir da pilha, os dados serão exibidos para executar a operação. Há também outra estrutura de dados, geralmente uma matriz para armazenar as variáveis locais. As variáveis locais recebem IDs, que são apenas os índices da matriz.
Vejamos as mnemônicas no
main()
método:iconst_0
: O valor constante0
é enviado para a pilha.istore_1
: O elemento superior da pilha é exibido e armazenado na variável local com o índice1
que é
x
.iload_1
: O valor no local1
cujo valorx
é0
, é empurrado para a pilha.iinc 1, 1
: O valor no local da memória1
é incrementado por1
. Entãox
agora se torna1
.istore_1
: O valor na parte superior da pilha é armazenado no local da memória1
. Isso é0
designado parax
substituir seu valor incrementado.Portanto, o valor de
x
não muda, resultando no loop infinito.fonte
++
), mas a variável é substituída mais tarde.int temp = x; x = x + 1; x = temp;
é melhor não usar uma tautologia no seu exemplo.No entanto "
=
" tem uma precedência de operador menor que "++
".Portanto,
x=x++;
deve avaliar da seguinte formax
preparado para atribuição (avaliado)x
incrementadox
atribuído ax
.fonte
++
tem precedência mais alta que=
em C e C ++, mas a instrução é indefinida nesses idiomas.Nenhuma das respostas foi muito boa, então aqui vai:
Quando você está escrevendo
int x = x++
, não está atribuindox
a si próprio com o novo valor, mas simx
com o valor de retorno dax++
expressão. Que passa a ser o valor original dex
, como sugerido na resposta de Colin Cochrane .Por diversão, teste o seguinte código:
O resultado será
O valor de retorno da expressão é o valor inicial de
x
, que é zero. Mais tarde, porém, ao ler o valor dex
, recebemos o valor atualizado, que é um deles.fonte
Já foi bem explicado por outros. Apenas incluo os links para as seções relevantes de especificação Java.
x = x ++ é uma expressão. Java seguirá a ordem de avaliação . Primeiro, avaliará a expressão x ++, que incrementará x e definirá o valor do resultado para o valor anterior de x . Em seguida, atribuirá o resultado da expressão à variável x. No final, x está de volta ao seu valor anterior.
fonte
Esta afirmação:
avalia assim:
x
para a pilha;x
;x
da pilha.Portanto, o valor é inalterado. Compare isso com:
que avalia como:
x
;x
para a pilha;x
da pilha.O que você quer é:
fonte
x++
trecho de código.x++;
sua soluçãox=x; x++;
e você estará fazendo o que afirma que o código original está fazendo.A resposta é bem direta. Tem a ver com a ordem em que as coisas são avaliadas.
x++
retorna o valor ex
depois incrementax
.Conseqüentemente, o valor da expressão
x++
é0
. Então você está atribuindox=0
cada vez no loop. Certamentex++
incrementa esse valor, mas isso acontece antes da atribuição.fonte
Em http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html
Para ilustrar, tente o seguinte:
Que imprimirá 1 e 0.
fonte
Você está efetivamente recebendo o seguinte comportamento.
A ideia é que o operador pós-incremento (x ++) aumente essa variável em questão DEPOIS de retornar seu valor para uso na equação em que é usado.
Editar: Adicionando um pouco por causa do comentário. Considere da seguinte maneira.
fonte
Você realmente não precisa do código da máquina para entender o que está acontecendo.
De acordo com as definições:
O operador de atribuição avalia a expressão do lado direito e a armazena em uma variável temporária.
1.1 O valor atual de x é copiado nessa variável temporária
1.2 x é incrementado agora.
A variável temporária é então copiada para o lado esquerdo da expressão, que é x por acaso! É por isso que o valor antigo de x é novamente copiado para si mesmo.
É bem simples.
fonte
Isso ocorre porque nunca é incrementado neste caso.
x++
usará o valor dele antes de incrementar como neste caso, será como:Mas se você fizer
++x;
isso, aumentará.fonte
O valor permanece em 0 porque o valor
x++
é 0. Nesse caso, não importa se o valor dex
é aumentado ou não, a atribuiçãox=0
é executada. Isso substituirá o valor incrementado temporário dex
(que foi 1 por um "tempo muito curto").fonte
x++
, não por toda a atribuiçãox=x++;
Isso funciona como você espera que o outro funcione. É a diferença entre prefixo e postfix.
fonte
Pense em x ++ como uma chamada de função que "retorna" o que X era antes do incremento (é por isso que é chamado de pós-incremento).
Portanto, a ordem da operação é:
1: armazene em cache o valor de x antes de incrementar
2: incremento x
3: retorne o valor em cache (x antes de ser incrementado)
4: o valor de retorno seja atribuído a x
fonte
Quando o ++ está no rhs, o resultado é retornado antes que o número seja incrementado. Mude para ++ x e teria sido bom. Java teria otimizado isso para executar uma única operação (a atribuição de x para x) em vez do incremento.
fonte
Bem, até onde eu posso ver, o erro ocorre devido à atribuição que substitui o valor incrementado, pelo valor anterior à incrementação, ou seja, desfaz o incremento.
Especificamente, a expressão "x ++" tem o valor de 'x' antes do incremento, em oposição a "++ x", que tem o valor de 'x' após a incrementação.
Se você estiver interessado em investigar o bytecode, examinaremos as três linhas em questão:
7: iload_1 # Colocará o valor da 2ª variável local na pilha
8: iinc 1,1 # aumentará a 2ª variável local com 1, observe que ela deixa a pilha intocada!
9: istore_1 # Irá aparecer no topo da pilha e salvar o valor desse elemento na segunda variável local
(você pode ler os efeitos de cada instrução da JVM aqui )
É por isso que o código acima fará um loop indefinidamente, enquanto a versão com ++ x não. O bytecode para ++ x deve parecer bem diferente, tanto quanto me lembro do compilador Java 1.3 que escrevi há pouco mais de um ano, o bytecode deve ser algo como isto:
Então, apenas trocando as duas primeiras linhas, altera a semântica para que o valor deixado no topo da pilha, após o incremento (ou seja, o 'valor' da expressão) seja o valor após o incremento.
fonte
Assim:
Enquanto que
Assim:
Obviamente, o resultado final é o mesmo que apenas
x++;
ou++x;
em uma linha por si só.fonte
por causa da afirmação acima, x nunca atinge 3;
fonte
Gostaria de saber se há algo na especificação Java que defina precisamente o comportamento disso. (A implicação óbvia dessa afirmação é que estou com preguiça de verificar.)
Nota do bytecode de Tom, as linhas principais são 7, 8 e 11. A linha 7 carrega x na pilha de computação. A linha 8 incrementa x. A linha 11 armazena o valor da pilha de volta em x. Em casos normais em que você não está atribuindo valores a si mesmos, acho que não haveria motivo para não carregar, armazenar e incrementar. Você obteria o mesmo resultado.
Tipo, suponha que você tenha um caso mais normal em que tenha escrito algo como: z = (x ++) + (y ++);
Se ele disse (pseudocódigo para pular detalhes técnicos)
ou
deve ser irrelevante. Qualquer implementação deve ser válida, eu acho.
Eu seria extremamente cauteloso ao escrever código que depende desse comportamento. Parece-me muito dependente da implementação, entre as rachaduras nas especificações. A única vez que faria diferença é se você fizesse algo louco, como o exemplo aqui, ou se você tivesse dois threads em execução e dependesse da ordem de avaliação na expressão.
fonte
Eu acho que porque em Java ++ tem uma precedência maior do que = (atribuição) ... Não é? Veja http://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ...
Da mesma forma, se você escrever x = x + 1 ... + tem uma precedência mais alta que = (atribuição)
fonte
++
tem precedência mais alta do que=
em C e C ++ também, mas a instrução é indefinida.A
x++
expressão é avaliada comox
. A++
parte afeta o valor após a avaliação , não após a declaração . assimx = x++
é efetivamente traduzido emfonte
Antes de aumentar o valor em um, o valor é atribuído à variável.
fonte
Está acontecendo porque é pós-incrementado. Isso significa que a variável é incrementada após a expressão ser avaliada.
x agora é 10, mas y é 9, o valor de x antes de ser incrementado.
Veja mais em Definição de pós-incremento .
fonte
x
/y
exemplo é diferente do código real e a diferença é relevante. Seu link nem menciona Java. Para duas das línguas que não menciona, a declaração em questão é indefinido.Verifique o código abaixo,
a saída será,
post increment
significa incrementar o valor e retornar o valor antes do incremento . É por isso que o valortemp
é0
. Então, o que setemp = i
e isso está em um loop (exceto para a primeira linha de código). assim como na pergunta !!!!fonte
O operador de incremento é aplicado à mesma variável que você está atribuindo. Isso está pedindo problemas. Estou certo de que você pode ver o valor da sua variável x durante a execução deste programa .... isso deve deixar claro por que o loop nunca termina.
fonte