Solución:
Permítanme decir esto muy claramente, porque la gente lo malinterpreta todo el tiempo:
El orden de evaluación de subexpresiones es independiente de asociatividad y precedencia. La asociatividad y la precedencia determinan en qué orden operadores se ejecutan pero no determinar en que orden el subexpresiones son evaluados. Su pregunta es sobre el orden en que subexpresiones son evaluados.
Considerar 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 suma). 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 alguna orden y aún obedecer las reglas de precedencia y asociatividad!
Necesitamos una regla no relacionado a 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 a su pregunta. En a[b] = b = 0
las reglas de asociatividad dicen que esto es a[b] = (b = 0);
pero eso no significa que el b=0
corre primero! Las reglas de precedencia dicen que la indexación tiene mayor precedencia que la asignación, pero eso no significa que el indexador se ejecute antes de la asignación 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 sensibles 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 para b
debe suceder antes de la asignación a a[b]
, porque la asignación de cero calcula el valor que se asigna en la operación de indexación. La precedencia y la asociatividad por sí solas no dicen nada sobre si el a[b]
es evaluado antes de o después los b=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 basado en precedencia y 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 generada 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 sucede?
- los
a[b]
está a la izquierda de lab=0
, entonces ela[b]
carreras primero, Resultando ena[1]
. Sin embargo, comprobando el validez de esta operación de indexación se retrasa. - Entonces el
b=0
sucede. - Luego la verificación de que
a
es válido ya[1]
está dentro del rango sucede - La asignación del valor a
a[1]
sucede al final.
Entonces, aunque en este específico En caso de que haya algunas sutilezas a considerar para esos casos de error raros que no deberían estar ocurriendo en el código correcto en primer lugar, en general puede razonar: las cosas de la izquierda pasan 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 en estas cosas todo el tiempo, incluso personas que deberían saberlo mejor. Yo 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, consulte 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.
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:
Si la expresión del operando de la izquierda es una expresión de acceso a una matriz (§15.13), se requieren muchos pasos:
- Primero, se evalúa la subexpresión de referencia a la matriz de la expresión de acceso a la matriz del operando de la izquierda. Si esta evaluación se completa abruptamente, entonces la expresión de asignación se completa abruptamente por la misma razón; la subexpresión de índice (de la expresión de acceso a la matriz de operandos de la izquierda) y el operando de la derecha no se evalúan y no se produce ninguna asignación.
- De lo contrario, se evalúa la subexpresión de índice de la expresión de acceso a la matriz de operandos de la izquierda. Si esta evaluación se completa abruptamente, entonces la expresión de asignación se completa abruptamente por la misma razón y el operando de la derecha no se evalúa y no ocurre ninguna asignación.
- De lo contrario, se evalúa el operando de la derecha. Si esta evaluación se completa abruptamente, entonces la expresión de asignación se completa abruptamente por la misma razón y no ocurre ninguna asignación.
[… it then goes on to describe the actual meaning of the assignment itself, which we can ignore here for brevity …]
En resumen, Java tiene un orden de evaluación muy definido que es casi 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. (La JLS recomienda que no escriba código que necesite este tipo de restricciones semánticas complejas, y yo también: ¡puedes meterte en problemas más que suficientes con solo una tarea 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 suficientemente bien 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 ahí lo tienes).
a[b] = b = 0;
1) el operador de indexación de matriz tiene mayor precedencia que el operador de asignación (consulte esta respuesta):
(a[b]) = b = 0;
2) Según 15.26. Operadores de asignación de JLS
Hay 12 operadores de asignación; todos son sintácticamente asociativos por la derecha (se agrupan de derecha a izquierda). Por lo tanto, a = b = c significa a = (b = c), que asigna el valor de c a b y luego asigna el valor de b a a.
(a[b]) = (b=0);
3) Según 15.7. Orden de evaluación de JLS
El lenguaje de programación Java garantiza que los operandos de los operadores parecen ser evaluados en un orden de evaluación específico, es decir, de izquierda a derecha.
y
El operando de la izquierda de un operador binario parece estar completamente evaluado antes de que se evalúe cualquier parte del operando de la derecha.
Entonces:
a) (a[b])
evaluado primero a a[1]
b) entonces (b=0)
evaluado a 0
C) (a[1] = 0)
evaluado en último lugar