2011-04-14 10 views
26

preguntó a un unrelated question donde tuve código como este:Java: si de retorno si retorno vs si-retorno-elseif-retorno

public boolean equals(Object obj) 
{ 
    if (this == obj) 
     return true; 

    if (obj == null) 
     return false; 

    if (getClass() != obj.getClass()) 
     return false; 

    // Check property values 
} 

Tengo un comentario que afirmaba que esto no era óptimo, y que en su lugar (si he entendido correctamente) debe 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 retorno, no puedo ver por qué ninguno de ellos debe 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.

¿Falta algo aquí? ¿Hay algo para eso? ¿Hay algunas optimizaciones del compilador o algo así o lo que sea?

Sé que esto es la optimización de micro 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!

+14

Dime el nombre del tipo que hizo ese comentario y lo rechazaré por eso;) –

+3

Aconsejaría no molestarme sobre tales optimizaciones de nivel micro. Siempre puedes encontrar puristas que dividen bien el cabello. Tales optimaciones son manejadas bien por el compilador y las posibilidades son que el primero ya se reduzca a la segunda versión (o viceversa dependiendo de lo que el compilador considere que es bueno). –

+1

@Jigar Joshi - un virtual (+1) en vez de levantar la mano :-) –

Respuesta

22

El código de bytes generado es idéntico para esos dos casos, por lo que es puramente una cuestión de estilo.

produje dos métodos e1 y e2 y ambos produjeron el código de bytes (leer 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 

omití el código puse después de eso para que se compile.

+3

@Jigar: no es necesario ser inteligente aquí, el compilador no tenía otra opción que generar el mismo código para todos los casos. No hay una instrucción 'else' en el código byte, solo un grupo de' if-condition-goto'. –

+1

La única astucia necesaria es que el compilador necesita saber que después de un 'ireturn' no necesita poner una instrucción' jump-to-the-end-of-the-if-else-cascade'. Y eso es bastante trivial de verificar. –

+2

@Jigar - * "Interesante, el compilador es muy inteligente. *" ¿Crees? Newsflash para ti Jigar. Todos los compiladores han estado haciendo esto desde el principio de los tiempos, desde que se inventaron los lenguajes de programación de tercera generación (e incluso desde que las personas comenzaron a usar macroinstrucciones en el ensamblaje). Bienvenido a la tecnología de compilación inventada hace casi 60 años. Ni siquiera necesita conocer la tecnología del compilador. La lógica simple dicta que estos dos conjuntos de instrucciones if (y if-else) ** son ** equivalentes. –

3

Piénsalo de esta manera. Cuando se ejecuta una declaración de devolución, el control abandona el método, por lo que else realmente no agrega ningún valor, a menos que quiera argumentar que agrega legibilidad (lo que realmente no creo que haga, pero otros pueden estar en desacuerdo).

Así que cuando usted tiene:

if (someCondition) 
    return 42; 

if (anotherCondition) 
    return 43; 

En realidad no hay ningún valor en la adición de un else a la segunda if.

De hecho, utilizo una herramienta al escribir el código C# llamado Resharper, y en realidad marcó 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.

+2

Estoy totalmente de acuerdo con Resharper allí ... Cuando veo esos puntos, tiendo a eliminarlos. De manera similar, me estremezco cuando veo si (cond) devuelve verdadero; else devuelve falso; ... – PhiLho

9

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 idéntico bytecode para los dos métodos.

Aquí es 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; 
    } 
} 

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 

Eso es, yo recomendaría usando 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. Incluyendo el else indica que el programador no se dio cuenta de que no era necesario.

+0

+1 para el análisis, ya lo señalé en un comentario anterior a la pregunta. Si prueba también la versión de retorno único, creo que volvería a ser exactamente igual: el compilador no se molestará en crear una variable y realizar operaciones de memoria cuando sepa que todo lo que está ahí es devolver los resultados. –

+0

Es interesante que tu compilador tenga una len de 27 y el compilador de @Joachim Sauer tenga 25. ¿Qué JDK usaste? – rajah9

+0

Estoy de acuerdo, incluyendo el resto indica que el programador no se dio cuenta de que no era necesario. –

5

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 las construcciones 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; 
} 
0

Creo que este código se puede mejorar un poco (te importa, es muy legible):

if (obj == null) 
     return false; 

    if (getClass() != obj.getClass()) 
     return false; 

El operador instanceof es equivalente a ambos combinados y es probablemente más rápido - menos código, y no hay invocaciones de métodos:

if (!(obj instanceof MyClass)) 
     return false; 

Pero qué sé ... Soy demasiado vago para analizar el código de bytes (nunca antes lo había hecho). :-p

+1

Debe tener en cuenta que 'getClass()' y 'instanceof' son * no * equivalentes. Si tiene dos clases 'A' y' B' donde 'B extiende A', entonces' getClass() 'siempre es' false', mientras que 'b instanceof A' es' true'. – beatngu13