Java: if-return-if-return vs if-return-elseif-return


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 afirmaba que esto no era óptimo, y que en su lugar (si entendí 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 return, realmente no puedo ver por qué ninguna de ellas debería ser más eficiente o más rápida que la otra. 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 retorno, no código extra se ejecutaría en cualquiera de ellos.

¿Me estoy perdiendo algo aquí? ¿Hay algo en ello? ¿Hay algunas optimizaciones del compilador o algo pasando o lo que sea?

Sé que esto es micro optimización y lo más probable es que me quede con la primera de cualquier manera, ya que creo que se ve más limpio con todos los ifs en la misma posición. Pero no puedo evitarlo; ¡tengo curiosidad!

Author: Community, 2011-04-14

5 answers

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 este código de byte (leído 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 que puse después de eso para compilarlo.

 22
Author: Joachim Sauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-14 12:51:03

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 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 el otro partes, pero yo diría lo contrario. Incluyendo el else indica que el programador no se dio cuenta de que era innecesario.

 9
Author: aioobe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-14 13:00:36

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 se desea evitar múltiples declaraciones de retorno 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;
}
 5
Author: Andreas_D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-14 12:54:29

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

Así que cuando tienes:

if (someCondition)
    return 42;

if (anotherCondition)
    return 43;

No hay realmente ningún valor en agregar un else al segundo if.

De hecho, uso una herramienta al escribir código C# llamada Resharper , y en realidad marcará el else como inútil código en estas situaciones. Así que creo que, en general, es mejor dejarlos fuera. Y como ya mencionó Joachim, el compilador los optimiza de todos modos.

 3
Author: dcp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-14 12:53:31

Creo que este código se puede mejorar un poco (eso sí, es muy legible):

  if (obj == null)
        return false;

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

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

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

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

 0
Author: Julius Musseau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-04-21 07:09:30