Estoy leyendo un texto de Java y obtuve el siguiente código:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
En el texto, el autor no dio una explicación clara y el efecto de la última línea es: a[1] = 0;
No estoy tan seguro de haber entendido: ¿cómo sucedió la evaluación?
java
operator-precedence
ipkiss
fuente
fuente
Respuestas:
Permítanme decir esto muy claramente, porque la gente lo entiende mal todo el tiempo:
El orden de evaluación de las subexpresiones es independiente tanto de la asociatividad como de la precedencia . La asociatividad y la precedencia determinan en qué orden se ejecutan los operadores , pero no determinan en qué orden se evalúan las subexpresiones . Su pregunta es sobre el orden en que se evalúan las subexpresiones .
Considere
A() + B() + C() * D()
. La multiplicación tiene mayor precedencia que la suma, y la suma es asociativa por la izquierda, por lo que esto es equivalente a(A() + B()) + (C() * D())
Pero saber eso solo te dice que la primera suma ocurrirá antes de la segunda suma, y que la multiplicación ocurrirá antes de la segunda suma. ¡No le dice en qué orden se llamarán A (), B (), C () y D ()! (Tampoco le dice si la multiplicación ocurre antes o después de la primera adición). Sería perfectamente posible obedecer las reglas de precedencia y asociatividad compilando esto como:d = D() // these four computations can happen in any order b = B() c = C() a = A() sum = a + b // these two computations can happen in any order product = c * d result = sum + product // this has to happen last
Allí se siguen todas las reglas de precedencia y asociatividad: la primera adición ocurre antes de la segunda adición y la multiplicación ocurre antes de la segunda adición. ¡Claramente podemos hacer las llamadas a A (), B (), C () y D () en cualquier orden y aún obedecer las reglas de precedencia y asociatividad!
Necesitamos una regla no relacionada con las reglas de precedencia y asociatividad para explicar el orden en que se evalúan las subexpresiones. La regla relevante en Java (y C #) es "las subexpresiones se evalúan de izquierda a derecha". Dado que A () aparece a la izquierda de C (), A () se evalúa primero, independientemente del hecho de que C () esté involucrado en una multiplicación y A () esté involucrado solo en una suma.
Entonces ahora tiene suficiente información para responder su pregunta. En
a[b] = b = 0
las reglas de asociatividad decimos que esto esa[b] = (b = 0);
pero eso no significa que elb=0
primero corre! Las reglas de precedencia dicen que la indexación tiene una precedencia mayor que la asignación, pero eso no significa que el indexador se ejecute antes de la asignación de más a la derecha .(ACTUALIZACIÓN: una versión anterior de esta respuesta tenía algunas omisiones pequeñas y prácticamente sin importancia en la sección que sigue que he corregido. También escribí un artículo de blog que describe por qué estas reglas son sensatas en Java y C # aquí: https: // ericlippert.com/2019/01/18/indexer-error-cases/ )
La precedencia y la asociatividad solo nos dicen que la asignación de cero a
b
debe ocurrir antes de la asignación dea[b]
, porque la asignación de cero calcula el valor que se asigna en la operación de indexación. Precedencia y asociatividad solos no dicen nada sobre si ela[b]
se evalúa antes o después de lab=0
.Nuevamente, esto es lo mismo que:
A()[B()] = C()
- Todo lo que sabemos es que la indexación debe realizarse antes de la asignación. No sabemos si A (), B () o C () se ejecuta primero en función de la precedencia y la asociatividad . Necesitamos otra regla que nos diga eso.La regla es, nuevamente, "cuando tenga la opción de qué hacer primero, vaya siempre de izquierda a derecha". Sin embargo, hay una arruga interesante en este escenario específico. ¿El efecto secundario de una excepción lanzada causado por una colección nula o un índice fuera de rango se considera parte del cálculo del lado izquierdo de la asignación o parte del cálculo de la asignación en sí? Java elige este último. (Por supuesto, esta es una distinción que solo importa si el código ya es incorrecto , porque el código correcto no elimina la referencia nula ni pasa un índice incorrecto en primer lugar).
¿Así que lo que pasa?
a[b]
está a la izquierda deb=0
, por lo que sea[b]
ejecuta primero , lo que da como resultadoa[1]
. Sin embargo, la verificación de la validez de esta operación de indexación se retrasa.b=0
sucede.a
es válida ya[1]
está dentro del rangoa[1]
ocurre en último lugar.Entonces, aunque en este caso específico hay algunas sutilezas a considerar para esos raros casos de error que no deberían ocurrir en el código correcto en primer lugar, en general puede razonar: las cosas de la izquierda suceden antes que las de la derecha . Esa es la regla que estás buscando. Hablar de precedencia y asociatividad es confuso e irrelevante.
La gente se equivoca todo el tiempo en estas cosas , incluso las que deberían saberlo mejor. He editado demasiados libros de programación que establecían las reglas incorrectamente, por lo que no es de extrañar que mucha gente tenga creencias completamente incorrectas sobre la relación entre precedencia / asociatividad y orden de evaluación, es decir, que en realidad no existe tal relación ; son independientes.
Si este tema le interesa, vea mis artículos sobre el tema para leer más:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
Se trata de C #, pero la mayoría de estas cosas se aplican igualmente bien a Java.
fuente
No obstante, la magistral respuesta de Eric Lippert no es de la debida ayuda porque habla de un idioma diferente. Esto es Java, donde Java Language Specification es la descripción definitiva de la semántica. En particular, §15.26.1 es relevante porque describe el orden de evaluación para el
=
operador (todos sabemos que es asociativo a la derecha, ¿no?). Reduciéndolo un poco a los bits que nos importan en esta pregunta:[… Luego pasa a describir el significado real de la tarea en sí, que podemos ignorar aquí por brevedad…]
En resumen, Java tiene un orden de evaluación muy definido que es exactamente de izquierda a derecha dentro de los argumentos de cualquier operador o llamada de método. Las asignaciones de matrices son uno de los casos más complejos, pero incluso allí sigue siendo L2R. (El JLS recomienda que no escriba código que necesite este tipo de restricciones semánticas complejas , y yo también: ¡puede meterse en problemas más que suficientes con solo una asignación por declaración!)
C y C ++ son definitivamente diferentes a Java en esta área: sus definiciones de lenguaje dejan el orden de evaluación sin definir deliberadamente para permitir más optimizaciones. Aparentemente, C # es como Java, pero no conozco su literatura lo suficiente como para poder señalar la definición formal. (Sin embargo, esto realmente varía según el idioma, Ruby es estrictamente L2R, al igual que Tcl, aunque carece de un operador de asignación per se por razones que no son relevantes aquí, y Python es L2R pero R2L con respecto a la asignación , lo cual me parece extraño, pero ya está. .)
fuente
a[-1]=c
,c
se evalúa, before-1
se reconoce como inválido.a[b] = b = 0;
1) el operador de indexación de matriz tiene mayor prioridad que el operador de asignación (consulte esta respuesta ):
(a[b]) = b = 0;
2) Según 15.26. Operadores de asignación de JLS
(a[b]) = (b=0);
3) Según 15.7. Orden de evaluación de JLS
y
Entonces:
a)
(a[b])
evaluado primero ena[1]
b) luego
(b=0)
evaluado para0
c)
(a[1] = 0)
evaluado en último lugarfuente
Tu código es equivalente a:
int[] a = {4,4}; int b = 1; c = b; b = 0; a[c] = b;
lo que explica el resultado.
fuente
Considere otro ejemplo más detallado a continuación.
Como regla general:
Es mejor tener una tabla de las reglas de orden de precedencia y asociatividad disponible para leer al resolver estas preguntas, por ejemplo http://introcs.cs.princeton.edu/java/11precedence/
He aquí un buen ejemplo:
System.out.println(3+100/10*2-13);
Pregunta: ¿Cuál es la salida de la línea anterior?
Respuesta: Aplicar las reglas de precedencia y asociatividad
Paso 1: De acuerdo con las reglas de precedencia: los operadores / y * tienen prioridad sobre los operadores + -. Por lo tanto, el punto de partida para ejecutar esta ecuación se reducirá a:
100/10*2
Paso 2: De acuerdo con las reglas y la precedencia: / y * son iguales en precedencia.
Como los operadores / y * tienen la misma precedencia, debemos observar la asociatividad entre esos operadores.
De acuerdo con las REGLAS DE ASOCIATIVIDAD de estos dos operadores en particular, comenzamos a ejecutar la ecuación de IZQUIERDA A DERECHA, es decir, 100/10 se ejecuta primero:
100/10*2 =100/10 =10*2 =20
Paso 3: La ecuación se encuentra ahora en el siguiente estado de ejecución:
=3+20-13
De acuerdo con las reglas y la precedencia: + y - son iguales en precedencia.
Ahora debemos observar la asociatividad entre los operadores + y - operadores. De acuerdo con la asociatividad de estos dos operadores particulares, comenzamos a ejecutar la ecuación de IZQUIERDA a DERECHA, es decir, se ejecuta primero 3 + 20:
=3+20 =23 =23-13 =10
10 es la salida correcta cuando se compila
Nuevamente, es importante tener una tabla de las reglas de orden de precedencia y asociatividad con usted al resolver estas preguntas, por ejemplo, http://introcs.cs.princeton.edu/java/11precedence/
fuente
10 - 4 - 3
.+
hay un operador unario (que tiene asociatividad de derecha a izquierda), pero los aditivos + y - tienen como multiplicativo * /% left a la asociatividad derecha.