Java: если vs. Switch
У меня есть фрагмент кода с a), который я заменил на b) чисто для удобочитаемости...
а)
if ( WORD[ INDEX ] == 'A' ) branch = BRANCH.A;
/* B through to Y */
if ( WORD[ INDEX ] == 'Z' ) branch = BRANCH.Z;
б)
switch ( WORD[ INDEX ] ) {
case 'A' : branch = BRANCH.A; break;
/* B through to Y */
case 'Z' : branch = BRANCH.Z; break;
}
... будет ли каскадная версия переключаться через все перестановки или переходить к случаю?
EDIT:
В некоторых из приведенных ниже ответов рассматриваются альтернативные подходы к вышеприведенному подходу.
Я включил следующее, чтобы предоставить контекст для его использования.
Причина, по которой я спросил, вопрос выше, состоял в том, что скорость добавления слов эмпирически улучшилась.
Это не производственный код, и был быстро взломан как PoC.
Следующее кажется подтверждением неудачи для мысленного эксперимента.
Мне может понадобиться гораздо больший корпус слов, чем тот, который я сейчас использую.
Ошибка возникает из-за того, что я не учитывал нулевые ссылки, все еще требующие памяти. (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 );
}
}
Ответы
Ответ 1
В байте -коде есть две формы переключателя: tableswitch
и lookupswitch
. Один предполагает плотный набор ключей, другой разреженный. См. Описание компилятора в спецификации JVM. Для перечислений порядковый номер найден, а затем код продолжается как случай int
. Я не совсем уверен, как будет реализована небольшая функция switch
on String
в JDK7.
Однако, сильно используемый код обычно скомпилирован в любой разумной JVM. Оптимизатор не совсем глуп. Не беспокойтесь об этом и следуйте обычной эвристике для оптимизации.
Ответ 2
Не беспокойтесь о производительности; используйте синтаксис, который лучше всего выражает то, что вы делаете. Только после того, как вы (а) продемонстрировали недостаток производительности; и (b) локализовать его на рутину, о которой идет речь, только тогда вы должны беспокоиться о производительности. Для моих денег здесь больше подходит синтаксис case.
Ответ 3
Похоже, вы перечислили значения, поэтому, возможно, перечисление в порядке?
enum BRANCH {
A,B, ... Y,Z;
}
Затем в вашем коде:
BRANCH branch = BRANCH.valueOf( WORD[ INDEX ] );
Кроме того, в вашем коде есть возможная ошибка, где "A" == "A"
может быть ложным в зависимости от идентификатора объекта "A".
Ответ 4
Не совсем ответ на ваш вопрос, но на самом деле в вашем коде есть ошибка, у вас должен быть разрыв после каждого случая:
switch ( WORD[ INDEX ] ) {
case 'A' : branch = BRANCH.A; break;
/* B through to Y */
case 'Z' : branch = BRANCH.Z; break;
}
Я не думаю, что различия в производительности здесь будут слишком значительными, но если вы действительно заботитесь о производительности, и если этот код выполняется очень часто, вот еще один вариант:
// Warning, untested code.
BRANCH[] branchLookUp = {BRANCH.A, BRANCH.B, ..., BRANCH.Z};
branch = branchLookUp[WORD[INDEX] - 'A'];
Обязательно инкапсулируйте это и хорошо документируйте.
Ответ 5
Честно говоря, я не думаю, что в этом случае важна производительность. Это действительно до компилятора и его оптимизации.
Ответ 6
Если у вас есть оператор switch с последовательными целыми значениями, в зависимости от языка, он может быть оптимизирован для таблицы ветвей, что очень быстро. Во всяком случае, это не медленнее!
Кроме того, использование if/else if будет улучшением, если, например, для случаев, когда ваши дела являются взаимоисключающими. Нет смысла делать еще 25 проверок после сопоставления A.
Но в принципе, любая разница в производительности незначительна, и вы должны использовать наиболее правильный синтаксис, который в этом случае является оператором switch. Не забудьте разделить ваши дела с перерывами.
Ответ 7
Здесь можно избежать всех утверждений case.
import java.util.HashMap;
public class Dictionary {
private static Dictionary ROOT;
private boolean terminus;
private final HashMap<Character, Dictionary> dictionaries = new HashMap<Character, Dictionary>();
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);
}
}
Ответ 8
Я знаю, что это совсем не то, о чем вы просите, но разве вы не делаете это?
public class Dictionary {
private static final Set<String> WORDS = new HashSet<String>();
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());
}
}
Вы просто ищете что-то более эффективное для памяти?
Ответ 9
Оператор switch
должен использовать хеш для выбора, к какому случаю обратиться. Оттуда каждый последующий случай также будет запущен, если нет операторов break
. Например, с вашим кодом, если вы включите X, он немедленно перейдет к X, затем Y, затем Z. См. Java Tutorial.
Ответ 10
switch
должен быть логарифмическим, а if
- линейным, если компилятор не может найти ничего умного. Но длинные switch
es сложны для чтения, а также подвержены ошибкам - как уже отмечалось, у коммутатора, который у вас есть выше, нет разрывов, и он пройдет через все случаи.
Почему бы не преобставить Map
вместо этого, а просто использовать Map.get()
?
private static final Map<Char, Whatever> BRANCHES = Collections.unmodifiableMap(new HashMap<Char, Whatever>() {{
put('A', BRANCH.A);
...
put('Z', BRANCH.Z);
}}
public void getBranch(char[] WORD, int INDEX) {
return BRANCHES.get(WORD[INDEX]);
}
Как отмечено выше, если BRANCH
является Enum
, это поведение должно быть должным образом в Enum
.
(Что такое WORD
, INDEX
и BRANCH
здесь, в любом случае? Из имен они должны быть константами, но вы не можете иметь постоянные массивы - содержимое также может быть изменено, t много смысла в создании постоянной "структуры", и, конечно, не было бы особого смысла в iffing или переключении на основе констант....)
Ответ 11
Я думаю, что это скорее вопрос стиля, а не производительности. Я думаю, что в этом случае оператор switch более уместен, чем если бы. Я не уверен, что есть большая разница в производительности.