statement - Java: if-return-if-return vs if-return-elseif-return
jstl if statement (5)
Hizo una pregunta no relacionada donde tenía un código como este:
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
// Check property values
}
Recibí un comentario que decía que esto no era óptimo, y que en su lugar (si lo entendía correctamente) debería hacer esto:
public boolean equals(Object obj)
{
if (this == obj)
return true;
else if (obj == null)
return false;
else if (getClass() != obj.getClass())
return false;
// Check property values
}
Debido a las declaraciones de devolución, realmente no puedo ver por qué alguno de ellos debería ser más eficiente o más rápido que el otro. Dado un determinado objeto, ambos métodos tendrían que hacer un número igual de comprobaciones por lo que puedo ver. Y debido a las declaraciones de devolución, no se ejecutará ningún código adicional en ninguna de ellas.
¿Me estoy perdiendo de algo? ¿Hay algo para eso? ¿Hay algunas optimizaciones del compilador o algo así o lo que sea?
Sé que esto es micro optimización y lo más probable es que me quede con el primero de todos modos, ya que creo que se ve más limpio con todos los ifs en la misma posición. Pero no puedo evitarlo; ¡Soy curioso!
Creo que este código se puede mejorar un poco (ojo, es muy legible):
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
El operador instanceof
es equivalente a ambos combinados y probablemente sea más rápido, menos código y no hay invocaciones de método:
if (!(obj instanceof MyClass))
return false;
Pero qué sé ... Soy demasiado perezoso para analizar el código de bytes (nunca antes lo había hecho). :-pag
El código de bytes generado es idéntico para esos dos casos, por lo que es puramente una cuestión de estilo.
e1
dos métodos e1
y e2
y ambos produjeron este código de bytes (lea usando javap -v
):
public boolean e1(java.lang.Object); Code: Stack=2, Locals=2, Args_size=2 0: aload_0 1: aload_1 2: if_acmpne 7 5: iconst_1 6: ireturn 7: aload_1 8: ifnonnull 13 11: iconst_0 12: ireturn 13: aload_0 14: invokevirtual #25; //Method java/lang/Object.getClass:()Ljava/lang/Class; 17: aload_1 18: invokevirtual #25; //Method java/lang/Object.getClass:()Ljava/lang/Class; 21: if_acmpeq 26 24: iconst_0 25: ireturn
Olvidé el código que puse después para hacerlo compilar.
Ninguno de los dos es más eficiente que el otro. El compilador puede ver fácilmente que los dos son idénticos, y de hecho Suns / Oracles javac
produce un bytecode idéntico para los dos métodos.
Aquí hay una clase IfTest
:
class IfTest {
public boolean eq1(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
return true;
}
public boolean eq2(Object obj) {
if (this == obj)
return true;
else if (obj == null)
return false;
else if (getClass() != obj.getClass())
return false;
return true;
}
}
Lo compilé con javac
y el desmontaje es el siguiente:
public boolean eq1(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: if_acmpne 7
5: iconst_1
6: ireturn
7: aload_1
8: ifnonnull 13
11: iconst_0
12: ireturn
13: aload_0
14: invokevirtual #2; //Method Object.getClass:()Ljava/lang/Class;
17: aload_1
18: invokevirtual #2; //Method Object.getClass:()Ljava/lang/Class;
21: if_acmpeq 26
24: iconst_0
25: ireturn
26: iconst_1
27: ireturn
public boolean eq2(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: if_acmpne 7
5: iconst_1
6: ireturn
7: aload_1
8: ifnonnull 13
11: iconst_0
12: ireturn
13: aload_0
14: invokevirtual #2; //Method Object.getClass:()Ljava/lang/Class;
17: aload_1
18: invokevirtual #2; //Method Object.getClass:()Ljava/lang/Class;
21: if_acmpeq 26
24: iconst_0
25: ireturn
26: iconst_1
27: ireturn
Es decir, recomendaría usar la primera versión (sin el else
). Algunas personas pueden argumentar que es más limpio con las partes más, pero yo diría lo contrario. Incluir el else
indica que el programador no se dio cuenta de que era innecesario.
No veo ninguna razón práctica para reemplazar una de esas implementaciones con la otra, en cualquier dirección.
El segundo ejemplo tendría sentido si quisiera evitar declaraciones de devolución múltiples en un método: algunas personas prefieren esa forma de codificación. Entonces necesitamos los constructores if-else if:
public boolean equals(Object obj)
{
boolean result = true;
if (this == obj)
result = true;
else if (obj == null)
result = false;
else if (getClass() != obj.getClass())
result = false;
return result;
}
Piénsalo de esta manera. Cuando se ejecuta una declaración de devolución, el control abandona el método, por lo que el else
realmente no agrega ningún valor, a menos que desee argumentar que agrega legibilidad (lo que realmente no creo que haga, pero otros pueden estar en desacuerdo).
Entonces cuando tienes:
if (someCondition)
return 42;
if (anotherCondition)
return 43;
Realmente no tiene ningún valor agregar un else
al segundo if
.
De hecho, uso una herramienta al escribir el código de C # llamado Resharper , y en realidad marcará el else
como código inútil en estas situaciones. Entonces, creo que, en general, es mejor dejarlos fuera. Y como ya mencionó Joachim, el compilador los optimiza de todos modos.