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:
-
f1()+f2()
- suma primero -
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úaf2()
, 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úaf3()
, 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 def2()
se agregaría al resultado def1()
en este punto, antes de calcularf3()
. -
el producto de
f2()
yf3()
se calcula, de acuerdo con la precedencia de la multiplicación sobre la suma. -
Se calcula la suma de (previamente evaluada)
f1()
yf2()*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()
luegof6()
luego se evalúa su producto. Esto es completamente análogo al caso def2()*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 evaluarf7()
, 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 def7()
se agregaría al resultado anterior, luego se calcularíaf8()
, 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.