java operator-precedence evaluation jls expression-evaluation

java - Orden de ejecución de f1()+f2()*f3() expresión y precedencia del operador en JLS



operator-precedence evaluation (5)

Dada una expresión f1() + f2()*f3() con 3 llamadas a métodos, java evalúa (operandos de) la operación de suma primero :

int result = f1() + f2()*f3(); f1 working f2 working f3 working

(Erróneamente) esperaba que f2() se llamara primero, luego f3() y finalmente f1() . Porque la multiplicación se evaluará antes de la suma.

Entonces, no entiendo JLS aquí, ¿qué me estoy perdiendo?

15.7.3. La evaluación respeta los paréntesis y la precedencia

El lenguaje de programación Java respeta el orden de evaluación indicado explícitamente entre paréntesis e implícitamente por la precedencia del operador .

¿Cómo se respeta exactamente la precedencia del operador en este ejemplo?

JLS menciona algunas excepciones en JLS JLS ( JLS invocación de métodos (§15.12.4), expresiones de referencia de métodos (§15.13.3)), pero no puedo aplicar ninguna de esas excepciones a mi ejemplo.

Entiendo que JLS garantiza que los operandos de una operación binaria se evalúen de izquierda a derecha. Pero en mi ejemplo, para comprender la secuencia de invocación del método, es necesario comprender qué operación (¡y sus dos operandos, respectivamente!) Se considera primero. ¿Me equivoco aquí? ¿Por qué?

Más ejemplos:

int result = f1() + f2()*f3() + f4() + f5()*f6() + (f7()+f8());

orgullosamente produce:

f1 working f2 working f3 working f4 working f5 working f6 working f7 working f8 working

Que es 100% de izquierda a derecha, independientemente de la precedencia del operador.

int result = f1() + f2() + f3()*f4();

produce:

f1 working f2 working f3 working f4 working

Aquí el compilador tenía dos opciones:

  1. f1()+f2() - suma primero
  2. f3()*f4() - multiplicación primero

¡Pero la "regla de evaluar de izquierda a derecha" funciona aquí como un encanto!

Este link implica que en tales ejemplos, las llamadas al método siempre se evalúan de izquierda a derecha, independientemente de las reglas de paréntesis y asociatividad.


Porque la multiplicación se evaluará antes de la suma.

Como parece decir en serio, esa no es una descripción razonable de la precedencia de la multiplicación sobre la suma, ni del efecto de la sección 15.7.3. La precedencia dice que tu expresión

f1() + f2()*f3()

se evalúa igual que

f1() + (f2() * f3())

, a diferencia de lo mismo que

(f1() + f2()) * f3()

La precedencia no habla sobre qué operando del operador + se evalúa primero, independientemente de la naturaleza de los operandos. Habla en cambio de lo que es cada operando. Esto impacta el orden de evaluación, ya que de las reglas de precedencia se deduce que el resultado de la multiplicación en su expresión original debe calcularse antes del resultado de la suma, pero eso no se refiere directamente a la pregunta de cuándo los valores de dos factores se evalúan entre sí o con otras subexpresiones.

Las reglas diferentes a la que usted citó son responsables del orden en el que se evalúan los dos operandos de un operador + o * : siempre el operando izquierdo primero y el operando derecho segundo.

¿Cómo se respeta exactamente la precedencia del operador en este ejemplo?

La salida del ejemplo no transmite información sobre si se respeta la precedencia, entendida correctamente. Se observaría el mismo resultado para las dos alternativas entre paréntesis anteriores, y la precedencia es sobre cuál de esas dos es equivalente a la expresión original.

En un sentido de orden de operaciones, la precedencia dice (solo) que el operando derecho de la suma es f2()*f3() , lo que significa que el producto debe calcularse antes de que la suma total pueda ser. JLS 15.7.3 no dice nada más o diferente de eso. Que esa subexpresión sea una multiplicación no tiene relación con su orden de evaluación en relación con el otro operando de la operación de suma.

Entiendo que JLS garantiza que los operandos de una operación binaria se evalúen de izquierda a derecha . Pero en mi ejemplo, para comprender la secuencia de invocación del método, es necesario comprender qué operación (¡y sus dos operandos, respectivamente!) Se considera primero. ¿Me equivoco aquí? ¿Por qué?

Sí, está equivocado y, a pesar de JLS, sus ejemplos proporcionan muy buena evidencia de ello.

El problema parece ser el bit "y sus dos operandos respectivamente". Su idea parece ser más o menos que Java debería mirar a través de la expresión, encontrar todas las operaciones de multiplicación y evaluarlas completamente, incluidos sus operandos, y solo después regresar y hacer lo mismo para las operaciones de suma. Pero eso no es necesario para observar la precedencia, y no es coherente con el orden de evaluación de izquierda a derecha de las operaciones de adición, y haría que el código Java sea mucho más difícil de escribir o leer para los humanos.

Consideremos su ejemplo más largo:

int result = f1() + f2()*f3() + f4() + f5()*f6() + (f7()+f8());

La evaluación procede de la siguiente manera:

  • f1() se evalúa primero, porque es el operando izquierdo de la primera operación, y los operandos de la operación deben evaluarse de izquierda a derecha
  • f2() se evalúa f2() , porque es parte del operando derecho a la suma (cuyo turno se va a evaluar), y específicamente el operando izquierdo de la multiplicación.
  • f3() se evalúa f3() , porque se está evaluando la subexpresión de multiplicación a la que pertenece, y es el operando correcto de esa multiplicación. La precedencia afecta el orden de las operaciones aquí al establecer que, de hecho, es el producto que se está evaluando. Si no fuera así, entonces, en su lugar, el resultado de f2() se agregaría al resultado de f1() en este punto, antes de calcular f3() .
  • el producto de f2() y f3() se calcula, de acuerdo con la precedencia de la multiplicación sobre la suma.
  • Se calcula la suma de (previamente evaluada) f1() y f2()*f3() . Esto surge de una combinación de precedencia de * sobre + ya discutida, y la asociatividad de izquierda a derecha de + , que establece que la suma es el operando izquierdo de la siguiente operación de suma y, por lo tanto, debe evaluarse antes del operando derecho de esa operación .
  • Se evalúa f4() , porque es el operando correcto de la operación de suma que se evalúa en ese punto.
  • Se calcula la segunda suma. Esto se debe nuevamente a la asociatividad de izquierda a derecha de + y al orden de evaluación de izquierda a derecha de la siguiente operación de suma.
  • f5() luego f6() luego se evalúa su producto. Esto es completamente análogo al caso de f2()*f3() , y por lo tanto es otro ejercicio de precedencia del operador.
  • el resultado de f5()*f6() , como el operando derecho de una operación de suma, se agrega al operando izquierdo. Esto es nuevamente análogo a los pasos anteriores.
  • Se evalúa f7() .
  • Debido a los paréntesis , a continuación se evalúa f8() , y luego su suma con el resultado previamente determinado de evaluar f7() , y luego ese resultado se agrega al resultado anterior para completar el cálculo. Sin los paréntesis, el orden sería diferente: el resultado de f7() se agregaría al resultado anterior, luego se calcularía f8() , luego se agregaría ese resultado.

El consejo más útil en mi opinión se encontró en esta discussion

JLS no es tan claro al respecto: la sección 15.7 sobre evaluación habla solo de operandos de una sola operación, dejando casos complejos de la vida real poco claros con muchas operaciones combinadas.


Realmente no veo un problema aquí. Las llamadas al método se evalúan a medida que se encuentran de izquierda a derecha. Esto no tiene nada que ver con la precedencia del operador.

Dado lo siguiente:

int v = f1() + f2() * f3(); System.out.println(v); public int f1() { System.out.println("f1()"); return 1; } public int f2() { System.out.println("f2()"); return 2; } public int f3() { System.out.println("f3()"); return 3; } }

Se imprime

f1 ()
f2 ()
f3 ()
7 7

Que es exactamente lo que esperaría.


Te falta el texto inmediatamente debajo de 15.7:

El lenguaje de programación Java garantiza que los operandos de los operadores parezcan evaluarse en un orden de evaluación específico, es decir, de izquierda a derecha.

Los valores de los operandos de una expresión se evalúan antes que la expresión misma. En su caso específico, la expresión más externa es f1() + [f2() * f3()] . Primero debemos evaluar f1() , luego debemos evaluar f2() * f3() . Dentro de eso, f2() se evalúa primero, luego f3() , luego la multiplicación (que devuelve el valor para el operando de + ).


JLS §15.17.2. Evaluar operandos antes de la operación:

El lenguaje de programación Java garantiza que cada operando de un operador (excepto los operadores condicionales && , || , y ? : Parece ser evaluado completamente antes de cualquier parte de
La operación en sí se realiza.

Por lo tanto, f1() , f2() y f3() se evalúan primero (de izquierda a derecha). Luego, se aplica la precedencia del operador.

Después de todo, puede observar el orden de ejecución en el código de bytes, que en mi caso es:

[...] INVOKESTATIC Main.f1 ()I INVOKESTATIC Main.f2 ()I INVOKESTATIC Main.f3 ()I IMUL IADD

Así es como se vería el árbol de expresión:

+ / / / / f1 * / / / / f2 f3

Las hojas se evalúan primero y la precedencia del operador se codifica en el árbol mismo.