Java: si vs. cambio

Tengo un código con a) que reemplacé con b) puramente por legibilidad …

un)

if ( WORD[ INDEX ] == 'A' ) branch = BRANCH.A; /* B through to Y */ if ( WORD[ INDEX ] == 'Z' ) branch = BRANCH.Z; 

segundo)

 switch ( WORD[ INDEX ] ) { case 'A' : branch = BRANCH.A; break; /* B through to Y */ case 'Z' : branch = BRANCH.Z; break; } 

… ¿la versión del conmutador pasará en cascada a través de todas las permutaciones o saltará a una caja?

EDITAR:

Algunas de las respuestas a continuación consideran enfoques alternativos al enfoque anterior.
He incluido lo siguiente para proporcionar un contexto para su uso.

La razón por la que pregunté, la Pregunta anterior, se debe a que la velocidad de agregar palabras mejoró empíricamente.

Este no es el código de producción de ninguna manera, y fue pirateado rápidamente como un PoC.

Lo siguiente parece ser una confirmación del fracaso de un experimento mental.
Puede que necesite un corpus de palabras mucho más grande que el que estoy usando actualmente.
La falla surge del hecho de que no tuve en cuenta las referencias nulas que aún requieren memoria. (Doh!)

 public class Dictionary { private static Dictionary ROOT; private boolean terminus; private Dictionary A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; private static Dictionary instantiate( final Dictionary DICTIONARY ) { return ( DICTIONARY == null ) ? new Dictionary() : DICTIONARY; } private Dictionary() { this.terminus = false; this.A = this.B = this.C = this.D = this.E = this.F = this.G = this.H = this.I = this.J = this.K = this.L = this.M = this.N = this.O = this.P = this.Q = this.R = this.S = this.T = this.U = this.V = this.W = this.X = this.Y = this.Z = null; } public static void add( final String...STRINGS ) { Dictionary.ROOT = Dictionary.instantiate( Dictionary.ROOT ); for ( final String STRING : STRINGS ) Dictionary.add( STRING.toUpperCase().toCharArray(), Dictionary.ROOT , 0, STRING.length() - 1 ); } private static void add( final char[] WORD, final Dictionary BRANCH, final int INDEX, final int INDEX_LIMIT ) { Dictionary branch = null; switch ( WORD[ INDEX ] ) { case 'A' : branch = BRANCH.A = Dictionary.instantiate( BRANCH.A ); break; case 'B' : branch = BRANCH.B = Dictionary.instantiate( BRANCH.B ); break; case 'C' : branch = BRANCH.C = Dictionary.instantiate( BRANCH.C ); break; case 'D' : branch = BRANCH.D = Dictionary.instantiate( BRANCH.D ); break; case 'E' : branch = BRANCH.E = Dictionary.instantiate( BRANCH.E ); break; case 'F' : branch = BRANCH.F = Dictionary.instantiate( BRANCH.F ); break; case 'G' : branch = BRANCH.G = Dictionary.instantiate( BRANCH.G ); break; case 'H' : branch = BRANCH.H = Dictionary.instantiate( BRANCH.H ); break; case 'I' : branch = BRANCH.I = Dictionary.instantiate( BRANCH.I ); break; case 'J' : branch = BRANCH.J = Dictionary.instantiate( BRANCH.J ); break; case 'K' : branch = BRANCH.K = Dictionary.instantiate( BRANCH.K ); break; case 'L' : branch = BRANCH.L = Dictionary.instantiate( BRANCH.L ); break; case 'M' : branch = BRANCH.M = Dictionary.instantiate( BRANCH.M ); break; case 'N' : branch = BRANCH.N = Dictionary.instantiate( BRANCH.N ); break; case 'O' : branch = BRANCH.O = Dictionary.instantiate( BRANCH.O ); break; case 'P' : branch = BRANCH.P = Dictionary.instantiate( BRANCH.P ); break; case 'Q' : branch = BRANCH.Q = Dictionary.instantiate( BRANCH.Q ); break; case 'R' : branch = BRANCH.R = Dictionary.instantiate( BRANCH.R ); break; case 'S' : branch = BRANCH.S = Dictionary.instantiate( BRANCH.S ); break; case 'T' : branch = BRANCH.T = Dictionary.instantiate( BRANCH.T ); break; case 'U' : branch = BRANCH.U = Dictionary.instantiate( BRANCH.U ); break; case 'V' : branch = BRANCH.V = Dictionary.instantiate( BRANCH.V ); break; case 'W' : branch = BRANCH.W = Dictionary.instantiate( BRANCH.W ); break; case 'X' : branch = BRANCH.X = Dictionary.instantiate( BRANCH.X ); break; case 'Y' : branch = BRANCH.Y = Dictionary.instantiate( BRANCH.Y ); break; case 'Z' : branch = BRANCH.Z = Dictionary.instantiate( BRANCH.Z ); break; } if ( INDEX == INDEX_LIMIT ) branch.terminus = true; else Dictionary.add( WORD, branch, INDEX + 1, INDEX_LIMIT ); } public static boolean is( final String STRING ) { Dictionary.ROOT = Dictionary.instantiate( Dictionary.ROOT ); return Dictionary.is( STRING.toUpperCase().toCharArray(), Dictionary.ROOT, 0, STRING.length() - 1 ); } private static boolean is( final char[] WORD, final Dictionary BRANCH, final int INDEX, final int INDEX_LIMIT ) { Dictionary branch = null; switch ( WORD[ INDEX ] ) { case 'A' : branch = BRANCH.A; break; case 'B' : branch = BRANCH.B; break; case 'C' : branch = BRANCH.C; break; case 'D' : branch = BRANCH.D; break; case 'E' : branch = BRANCH.E; break; case 'F' : branch = BRANCH.F; break; case 'G' : branch = BRANCH.G; break; case 'H' : branch = BRANCH.H; break; case 'I' : branch = BRANCH.I; break; case 'J' : branch = BRANCH.J; break; case 'K' : branch = BRANCH.K; break; case 'L' : branch = BRANCH.L; break; case 'M' : branch = BRANCH.M; break; case 'N' : branch = BRANCH.N; break; case 'O' : branch = BRANCH.O; break; case 'P' : branch = BRANCH.P; break; case 'Q' : branch = BRANCH.Q; break; case 'R' : branch = BRANCH.R; break; case 'S' : branch = BRANCH.S; break; case 'T' : branch = BRANCH.T; break; case 'U' : branch = BRANCH.U; break; case 'V' : branch = BRANCH.V; break; case 'W' : branch = BRANCH.W; break; case 'X' : branch = BRANCH.X; break; case 'Y' : branch = BRANCH.Y; break; case 'Z' : branch = BRANCH.Z; break; } if ( branch == null ) return false; if ( INDEX == INDEX_LIMIT ) return branch.terminus; else return Dictionary.is( WORD, branch, INDEX + 1, INDEX_LIMIT ); } } 

En bytecode hay dos formas de cambio: cambio de tableswitch y lookupswitch . Uno asume un conjunto denso de claves, el otro es escaso. Consulte la descripción del interruptor de comstackción en la especificación de JVM . Para enumeraciones, se encuentra el ordinal y luego el código continúa como el caso int . No estoy del todo seguro de cómo se implementará el switch propuesto en String pequeña característica String en JDK7.

Sin embargo, el código muy usado normalmente se comstack en cualquier JVM sensible. El optimizador no es del todo estúpido. No te preocupes por eso, y sigue la heurística habitual para la optimización.

No te preocupes por el rendimiento; usa la syntax que mejor expresa lo que estás haciendo. Solo después de que (a) haya demostrado una deficiencia en el rendimiento; y (b) lo localizó en la rutina en cuestión, solo entonces debería preocuparse por el rendimiento. Por mi dinero, la syntax del caso es más apropiada aquí.

Parece que ha enumerado los valores, entonces ¿quizás una enumeración está en orden?

 enum BRANCH { A,B, ... Y,Z; } 

Luego en tu código:

 BRANCH branch = BRANCH.valueOf( WORD[ INDEX ] ); 

Además, hay un error posible en su código donde "A" == "A" puede ser falso dependiendo de la identidad del objeto de la “A”.

No es exactamente una respuesta a su pregunta, pero en realidad hay un error en su código, debe tener un descanso después de cada caso:

 switch ( WORD[ INDEX ] ) { case 'A' : branch = BRANCH.A; break; /* B through to Y */ case 'Z' : branch = BRANCH.Z; break; } 

No creo que las diferencias de rendimiento vayan a ser demasiado significativas aquí, pero si realmente te importa el rendimiento, y si este código se ejecuta con mucha frecuencia, aquí hay otra opción:

 // Warning, untested code. BRANCH[] branchLookUp = {BRANCH.A, BRANCH.B, ..., BRANCH.Z}; branch = branchLookUp[WORD[INDEX] - 'A']; 

Asegúrese de encapsular esto y documentarlo bien.

Honestamente, no creo que el rendimiento importe en este caso. Depende realmente del comstackdor y su optimización.

Si tiene una sentencia switch con valores integrales consecutivos, según el idioma, puede optimizarse para una tabla de ramificación, que es muy rápida. ¡No es más lento, de todos modos!

Además, usar if / else sería una mejora sobre if, para casos como este en el que sus casos son mutuamente excluyentes. No tiene sentido hacer 25 controles más después de igualar a A.

Pero, básicamente, cualquier diferencia de rendimiento es insignificante, y debe utilizar la syntax más correcta, que en este caso es la statement de cambio. Sin embargo, asegúrese de separar sus casos con descansos.

Aquí hay una manera de evitar todas las declaraciones de casos.

 import java.util.HashMap; public class Dictionary { private static Dictionary ROOT; private boolean terminus; private final HashMap dictionaries = new HashMap(); private void ensureBranch(char c) { if (getBranch(c) != null) return; dictionaries.put(c, new Dictionary()); } private Dictionary getBranch(char c) { return dictionaries.get(c); } public static boolean is(final String string) { ensureRoot(); return is(chars(string), ROOT, 0, string.length() - 1); } public static void add(final String... strings) { ensureRoot(); for (final String string : strings) add(chars(string), ROOT, 0, string.length() - 1); } private static void ensureRoot() { if (ROOT == null) ROOT = new Dictionary(); } private static char[] chars(final String string) { return string.toUpperCase().toCharArray(); } private Dictionary() { this.terminus = false; } private static void add(final char[] word, final Dictionary dictionary, final int index, final int limit) { Dictionary branch = getBranch(word, dictionary, index); if (index == limit) branch.terminus = true; else add(word, branch, index + 1, limit); } private static Dictionary getBranch(final char[] word, final Dictionary dictionary, final int index) { final char c = word[index]; dictionary.ensureBranch(c); return dictionary.getBranch(c); } private static boolean is(final char[] word, final Dictionary dictionary, final int index, final int limit) { Dictionary branch = dictionary.getBranch(word[index]); if (branch == null) return false; if (index == limit) return branch.terminus; return is(word, branch, index + 1, limit); } } 

Sé que esto no es lo que estás preguntando en absoluto, ¿pero no estás haciendo esto?

 public class Dictionary { private static final Set WORDS = new HashSet(); public static void add(final String... STRINGS) { for (String str : STRINGS) { WORDS.add(str.toUpperCase()); } } public static boolean is(final String STRING) { return WORDS.contains(STRING.toUpperCase()); } } 

¿Estás simplemente buscando algo un poco más eficiente con la memoria?

La instrucción switch debe usar un hash para seleccionar a qué caso ir. A partir de ahí, cada caso subsiguiente también se ejecutará si no hay declaraciones de break . Por ejemplo, con su código, si enciende X, irá inmediatamente a X, luego a Y, luego a Z. Consulte el Tutorial de Java .

El switch debe ser logarítmico y el lineal if , suponiendo que el comstackdor no puede encontrar nada inteligente. Pero los switch largos son difíciles de leer y propensos a errores, como se señaló, el cambio que tienes arriba no tiene interrupciones, y va a caer en todos los casos.

¿Por qué no rellenar un Map lugar y simplemente usar Map.get() ?

 private static final Map BRANCHES = Collections.unmodifiableMap(new HashMap() {{ put('A', BRANCH.A); ... put('Z', BRANCH.Z); }} public void getBranch(char[] WORD, int INDEX) { return BRANCHES.get(WORD[INDEX]); } 

Como se señaló anteriormente, si BRANCH es un Enum , este comportamiento debería estar adecuadamente en el Enum .

(¿Qué son WORD , INDEX y BRANCH aquí, de todos modos? A partir de los nombres, deben ser constantes, pero realmente no se pueden tener arrays constantes, los contenidos son siempre modificables, no tendría sentido crear un constante ” struct “; y ciertamente no tendría mucho sentido iffing o switching basado en constantes …)

Creo que esto es más una cuestión de estilo que de rendimiento. Creo que en este caso la statement de cambio es más apropiada que si. No estoy seguro de que haya mucha diferencia en el rendimiento.