Por que usar String em um bloco switch é mais eficiente do que em um bloco if-else? – java desempenho if
Pergunta:
De acordo com a documentação do Java:
The Java compiler generates generally more efficient bytecode from switch statements that use String objects than from chained if-then-else statements.
Sem contar o fato de que utilizar .equals() torna case-sensitive, de que forma precisamente este código é mais “eficiente”?
Autor da pergunta Machado
Comunidade
A principal razão neste caso é que ele não compara as strings com equals() e sim com hashCode(). Depois de compilado cada case guardará o hash da string e não a string em si. Aí ele gera o hash da variável que está sendo usada no switch e compara estes valores inteiros que é muito mais rápido que comparar uma sequência de caracteres.
Com isto permite haver um ganho na busca porque o switch usa uma tabela de lookup ao invés de percorrer cada condição. O if é O(N) e o switch costuma ser O(1).
Bytecode comparado (retirado dessa resposta no SO):
Compiled from "CompileSwitch.java"
public class CompileSwitch {
public CompileSwitch();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16 // String C
2: astore_1
3: ldc #18 // String A
5: aload_1
6: invokevirtual #20 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
9: ifne 28
12: ldc #26 // String B
14: aload_1
15: invokevirtual #20 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
18: ifne 28
21: ldc #16 // String C
23: aload_1
24: invokevirtual #20 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
27: pop
28: return
}
Switch
Compiled from "CompileSwitch.java"
public class CompileSwitch {
public CompileSwitch();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16 // String C
2: astore_1
3: aload_1
4: dup
5: astore_2
6: invokevirtual #18 // Method java/lang/String.hashCode:()I
9: lookupswitch { // 3
65: 44
66: 56
67: 68
default: 77
}
44: aload_2
45: ldc #24 // String A
47: invokevirtual #26 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
50: ifne 77
53: goto 77
56: aload_2
57: ldc #30 // String B
59: invokevirtual #26 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
62: ifne 77
65: goto 77
68: aload_2
69: ldc #16 // String C
71: invokevirtual #26 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
74: ifne 77
77: return
}
Ainda que o código seja maior, ele executa de forma mais rápida por não ter que percorrer todo ele como no anterior.
Note que há uma comparação da string para confirmar se não houve colisão do hash mas ela é executada só para o caso que deu hash igual e não para todas condições.
Obviamente que a diferença fica mais perceptível quando há vários cases. Com poucos pode até haver perda de performance. E claro que isto vai depender também da ordem do que precisa ser achado. Se o primeiro if satisfizer a condição é provavelmente mais rápido que o switch (não garanto porque depende de implementação).
Outra coia que pode fazer o if ser mais rápido é se a string for muito curta. Uma comparação direta acaba sendo mais rápida que ter que gerar o hash para depois comparar seu resultado.



