Estou lendo algum texto Java e obtive o seguinte código:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
No texto, o autor não deu uma explicação clara e o efeito da última linha é: a[1] = 0;
Não tenho tanta certeza de entender: como aconteceu a avaliação?
java
operator-precedence
ipkiss
fonte
fonte
Respostas:
Deixe-me dizer isso muito claramente, porque as pessoas não entendem isso o tempo todo:
A ordem de avaliação das subexpressões é independente da associatividade e da precedência . A associatividade e a precedência determinam em que ordem os operadores são executados, mas não determinam em que ordem as subexpressões são avaliadas. Sua pergunta é sobre a ordem em que as subexpressões são avaliadas.
Considere
A() + B() + C() * D()
. A multiplicação tem precedência mais alta do que a adição, e a adição é associativa à esquerda, então isso é equivalente a(A() + B()) + (C() * D())
Mas saber isso apenas informa que a primeira adição acontecerá antes da segunda adição e que a multiplicação acontecerá antes da segunda adição. Ele não diz em que ordem A (), B (), C () e D () serão chamados! (Também não informa se a multiplicação acontece antes ou depois da primeira adição.) Seria perfeitamente possível obedecer às regras de precedência e associatividade compilando isso como:Todas as regras de precedência e associatividade são seguidas lá - a primeira adição acontece antes da segunda adição, e a multiplicação acontece antes da segunda adição. É claro que podemos fazer as chamadas para A (), B (), C () e D () em qualquer ordem e ainda obedecer às regras de precedência e associatividade!
Precisamos de uma regra não relacionada às regras de precedência e associatividade para explicar a ordem em que as subexpressões são avaliadas. A regra relevante em Java (e C #) é "subexpressões são avaliadas da esquerda para a direita". Visto que A () aparece à esquerda de C (), A () é avaliado primeiro, independentemente do fato de que C () está envolvido em uma multiplicação e A () está envolvido apenas em uma adição.
Agora você tem informações suficientes para responder à sua pergunta. Nas
a[b] = b = 0
regras de associatividade, diga que isso é,a[b] = (b = 0);
mas isso não significa que ob=0
primeiro é executado! As regras de precedência dizem que a indexação é uma precedência mais alta do que a atribuição, mas isso não significa que o indexador é executado antes da atribuição mais à direita .(ATUALIZAÇÃO: uma versão anterior desta resposta tinha algumas omissões pequenas e praticamente sem importância na seção a seguir que eu corrigi. Também escrevi um artigo de blog descrevendo por que essas regras são razoáveis em Java e C # aqui: https: // ericlippert.com/2019/01/18/indexer-error-cases/ )
A precedência e a associatividade apenas nos dizem que a atribuição de zero a
b
deve acontecer antes da atribuição aa[b]
, porque a atribuição de zero calcula o valor atribuído na operação de indexação. A precedência e a associatividade sozinhas não dizem nada sobre se oa[b]
é avaliado antes ou depois dob=0
.Novamente, isso é exatamente o mesmo que:
A()[B()] = C()
- Tudo o que sabemos é que a indexação deve acontecer antes da atribuição. Não sabemos se A (), B () ou C () é executado primeiro com base na precedência e na associatividade . Precisamos de outra regra para nos dizer isso.A regra é, novamente, "quando você tiver uma escolha sobre o que fazer primeiro, vá sempre da esquerda para a direita". No entanto, há uma ruga interessante neste cenário específico. O efeito colateral de uma exceção lançada é causado por uma coleção nula ou índice fora do intervalo considerado parte do cálculo do lado esquerdo da atribuição ou parte do cálculo da própria atribuição? Java escolhe o último. (Claro, esta é uma distinção que só importa se o código já estiver errado , porque o código correto não cancela a referência de nulo ou passa um índice incorreto em primeiro lugar.)
Então o que acontece?
a[b]
está à esquerda dob=0
, portanto, oa[b]
é executado primeiro , resultando ema[1]
. No entanto, a verificação da validade desta operação de indexação está atrasada.b=0
acontece.a
é válida ea[1]
está dentro do intervalo acontecea[1]
acontece por último.Portanto, embora neste caso específico haja algumas sutilezas a serem consideradas para aqueles raros casos de erro que não deveriam estar ocorrendo no código correto em primeiro lugar, em geral você pode raciocinar: as coisas à esquerda acontecem antes das coisas à direita . Essa é a regra que você está procurando. Falar de precedência e associatividade é confuso e irrelevante.
As pessoas entendem isso errado o tempo todo , até mesmo pessoas que deveriam saber disso. Eu editei muitos livros de programação que declaravam as regras incorretamente, então não é surpresa que muitas pessoas tenham crenças completamente incorretas sobre a relação entre precedência / associatividade e ordem de avaliação - ou seja, que na realidade não existe tal relação ; eles são independentes.
Se este tópico interessar a você, veja meus artigos sobre o assunto para leitura adicional:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
Eles são sobre C #, mas a maioria dessas coisas se aplica igualmente bem ao Java.
fonte
A resposta magistral de Eric Lippert, no entanto, não é propriamente útil porque está falando sobre um idioma diferente. Este é o Java, onde a Especificação da linguagem Java é a descrição definitiva da semântica. Em particular, §15.26.1 é relevante porque descreve a ordem de avaliação do
=
operador (todos nós sabemos que é associativo à direita, sim?). Cortando um pouco nas partes que nos interessam nesta questão:[... em seguida, descreve o significado real da atribuição em si, que podemos ignorar aqui por brevidade ...]
Resumindo, o Java tem uma ordem de avaliação bem definida que é exatamente da esquerda para a direita nos argumentos de qualquer operador ou chamada de método. As atribuições de array são um dos casos mais complexos, mas mesmo assim ainda é L2R. (O JLS recomenda que você não escreva código que precise desses tipos de restrições semânticas complexas , e eu também: você pode ter problemas mais do que suficientes com apenas uma atribuição por instrução!)
C e C ++ são definitivamente diferentes de Java nesta área: suas definições de linguagem deixam a ordem de avaliação indefinida deliberadamente para permitir mais otimizações. C # é aparentemente como Java, mas não conheço sua literatura bem o suficiente para apontar para a definição formal. (Isso realmente varia de acordo com a linguagem, Ruby é estritamente L2R, assim como Tcl - embora falte um operador de atribuição per se por razões não relevantes aqui - e Python é L2R, mas R2L em relação à atribuição , o que eu acho estranho, mas aí está .)
fonte
a[-1]=c
,c
é avaliado, antes de-1
ser reconhecido como inválido.1) o operador de indexação de matriz tem maior precedência do que o operador de atribuição (veja esta resposta ):
2) De acordo com 15.26. Operadores de atribuição de JLS
3) De acordo com 15.7. Ordem de Avaliação de JLS
e
Assim:
a)
(a[b])
avaliado primeiro paraa[1]
b) então
(b=0)
avaliado para0
c)
(a[1] = 0)
avaliado por últimofonte
Seu código é equivalente a:
o que explica o resultado.
fonte
Considere outro exemplo mais detalhado abaixo.
Como regra geral:
É melhor ter uma tabela das Regras da Ordem de Precedência e Associatividade disponível para leitura ao resolver essas questões, por exemplo, http://introcs.cs.princeton.edu/java/11precedence/
Aqui está um bom exemplo:
Pergunta: Qual é o resultado da linha acima?
Resposta: Aplique as Regras de Precedência e Associatividade
Etapa 1: De acordo com as regras de precedência: os operadores / e * têm prioridade sobre os operadores + -. Portanto, o ponto de partida para executar esta equação será reduzido a:
Etapa 2: De acordo com as regras e precedência: / e * são iguais em precedência.
Como os operadores / e * são iguais em precedência, precisamos examinar a associatividade entre esses operadores.
De acordo com as REGRAS DE ASSOCIATIVIDADE destes dois operadores em particular, começamos a executar a equação da ESQUERDA PARA A DIREITA, ou seja, 100/10 é executado primeiro:
Etapa 3: a equação agora está no seguinte estado de execução:
De acordo com as regras e precedência: + e - são iguais em precedência.
Agora precisamos olhar para a associatividade entre os operadores + e - operadores. De acordo com a associatividade destes dois operadores particulares, começamos a executar a equação da ESQUERDA para a DIREITA, ou seja, 3 + 20 é executado primeiro:
10 é a saída correta quando compilado
Mais uma vez, é importante ter uma tabela das Regras da Ordem de Precedência e Associatividade ao resolver essas questões, por exemplo, http://introcs.cs.princeton.edu/java/11precedence/
fonte
10 - 4 - 3
.+
é um operador unário (que tem associatividade da direita para a esquerda), mas os aditivos + e - têm apenas multiplicativos * /% à esquerda para a associatividade correta.