Los siguientes rendimientos de código si un dado String s
es igual a cualquier otra de las cuerdas no modificables. El método utiliza el switch
-statement para hacerlo:
public class SwitchOnString {
public static boolean equalsAny(String s) {
switch (s) {
case "string 1":
return true;
case "string 2":
return true;
default:
return false;
}
}
}
De acuerdo con la Máquina Virtual de Java Especificación (JMS) 3.10 Compilación Cambia "
Recopilación de sentencias switch utiliza los tableswitch y lookupswitch instrucciones.
Además
tableswitch y lookupswitch instrucciones operan únicamente en
int
los datos.
He leído el capítulo 3.10, pero no encontró ningún String
mencionado.
El sólo una frase que viene indirectamente cerca es:
Otros tipos numéricos deben ser reducidas a tipo int para su uso en un interruptor.
Pregunta 1:
¿Es String
en este contexto también un tipo numérico? Hice o me he perdido algo?
Una javap -c
de las clases SwitchOnString
espectáculos:
Compiled from "SwitchOnString.java"
public class playground.SwitchOnString {
public playground.SwitchOnString();
...
public static boolean equalsAny(java.lang.String);
Code:
0: aload_0
1: dup
2: astore_1
3: invokevirtual #16 // Method java/lang/String.hashCode:()I
6: lookupswitch { // 2
1117855161: 32
1117855162: 44
default: 60
}
...
}
Es evidente que los hashCode
valores se utilizan como int
teclas situadas de los case
s. Esto probablemente coincide:
Los lookupswitch pares de instrucciones
int
claves (los valores de las etiquetas de caso) ...
Pasando a tableswitch y lookupswitch las JMS dice:
El tableswitch instrucción se utiliza cuando los casos del interruptor se pueden representar eficientemente como índices en una tabla de compensaciones de destino. (...) Cuando los casos del interruptor son escasos, la representación de la tabla de la tableswitch instrucción se vuelve ineficiente en términos de espacio. El lookupswitch instrucción puede ser utilizado en su lugar.
Si consigo este derecho, entonces el más los casos se vuelven escasos, más probablemente el lookupswitch será utilizado.
Pregunta 2:
pero mirando el código de bytes:
dos casos de cuerda lo suficientemente escasa para compilar switch
a lookupswitch ? ¿O cada interruptor en String
ser compilado para lookupswitch ?
La especificación no dice cómo compilar switch
las declaraciones, que es hasta el compilador.
En ese sentido, la declaración JVM, “Otros tipos numéricos debe reducirse a escribir int
para su uso en un switch
” no dice que el lenguaje de programación Java va a hacer tal conversión ni que String
o Enum
son tipos numéricos. Es decir long
, float
y double
son tipos numéricos, pero no hay soporte para el uso con switch
las declaraciones en el lenguaje de programación Java.
Por lo que el lenguaje de especificación dice que switch
más String
se apoya, por lo tanto, los compiladores deben encontrar una manera de compilar a código de bytes. El uso de una propiedad invariante como el código hash es una solución común, pero, en principio, otras propiedades como la longitud o un carácter arbitrario podría ser utilizado también.
Como se discutió en “ ¿Por qué cambiar de cadena compila en dos interruptores ” y “ interruptor de cadena de Java 7 decompiled: instrucción inesperada ”, javac
genera actualmente dos instrucciones de conmutación en el nivel de código de bytes cuando se compilan switch
sobre String
los valores (TJE también genera dos instrucciones, pero los detalles pueden ser diferentes) .
A continuación, el compilador tiene que escoger bien, una lookupswitch
o tableswitch
instrucción. javac
hace uso tableswitch
cuando los números no son escasos, pero sólo si la instrucción tiene más de dos etiquetas de caso.
Así que cuando compilo el siguiente método:
public static char two(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
}
return 0;
}
yo obtengo
public static char two(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 2
97: 36
98: 50
default: 61
}
36: aload_1
37: ldc #10 // String a
39: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 61
45: iconst_0
46: istore_2
47: goto 61
50: aload_1
51: ldc #12 // String b
53: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 61
59: iconst_1
60: istore_2
61: iload_2
62: lookupswitch { // 2
0: 88
1: 91
default: 94
}
88: bipush 97
90: ireturn
91: bipush 98
93: ireturn
94: iconst_0
95: ireturn
pero cuando compilo,
public static char three(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
case "c": return 'c';
}
return 0;
}
yo obtengo
public static char three(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: tableswitch { // 97 to 99
97: 36
98: 50
99: 64
default: 75
}
36: aload_1
37: ldc #10 // String a
39: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq 75
45: iconst_0
46: istore_2
47: goto 75
50: aload_1
51: ldc #12 // String b
53: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq 75
59: iconst_1
60: istore_2
61: goto 75
64: aload_1
65: ldc #13 // String c
67: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 75
73: iconst_2
74: istore_2
75: iload_2
76: tableswitch { // 0 to 2
0: 104
1: 107
2: 110
default: 113
}
104: bipush 97
106: ireturn
107: bipush 98
109: ireturn
110: bipush 99
112: ireturn
113: iconst_0
114: ireturn
No está claro por qué javac
hace esta elección. Si bien tableswitch
tiene una mayor huella de base (una palabra adicional 32 bits) en comparación con lookupswitch
, todavía sería más corta en bytecode, incluso para el dos case
escenario etiquetas.
Pero la consistencia de la decisión puede ser demostrado con la segunda declaración, que siempre tendrá el mismo rango de valores, pero se compila en lookupswitch
o tableswitch
que sólo depende de la cantidad de etiquetas. Así que cuando se utilizan los valores verdaderamente escasas:
public static char three(String s) {
switch(s) {
case "a": return 'a';
case "b": return 'b';
case "": return 0;
}
return 0;
}
compila a
public static char three(java.lang.String);
Code:
0: aload_0
1: astore_1
2: iconst_m1
3: istore_2
4: aload_1
5: invokevirtual #9 // Method java/lang/String.hashCode:()I
8: lookupswitch { // 3
0: 72
97: 44
98: 58
default: 83
}
44: aload_1
45: ldc #10 // String a
47: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
50: ifeq 83
53: iconst_0
54: istore_2
55: goto 83
58: aload_1
59: ldc #12 // String b
61: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
64: ifeq 83
67: iconst_1
68: istore_2
69: goto 83
72: aload_1
73: ldc #13 // String
75: invokevirtual #11 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
78: ifeq 83
81: iconst_2
82: istore_2
83: iload_2
84: tableswitch { // 0 to 2
0: 112
1: 115
2: 118
default: 120
}
112: bipush 97
114: ireturn
115: bipush 98
117: ireturn
118: iconst_0
119: ireturn
120: iconst_0
121: ireturn
el uso lookupswitch
de los códigos hash escaso, pero tableswitch
para el segundo interruptor.