Я ценю, что все, что может быть сделано в статусе переключения, может выполняться с помощью инструкции if else.
Но существуют ли стилистические правила для того, когда нужно использовать коммутатор, а не в противном случае.
Я ценю, что все, что может быть сделано в статусе переключения, может выполняться с помощью инструкции if else.
Но существуют ли стилистические правила для того, когда нужно использовать коммутатор, а не в противном случае.
Хорошо, switch
во многих случаях "легче", чем лестница if
/else if
, на мой взгляд. В принципе, у вас не так много синтаксиса с фигурными скобками и скобками на пути вашего кода. При этом switch
наследует синтаксис Си. Это означает, что у вас есть break
и только одна область переменных, если вы не вводите новые блоки.
Тем не менее, компилятор может оптимизировать операторы switch
в таблице поиска и выполнять проверку времени компиляции для литералов при работе с перечислениями. Поэтому я бы предположил, что обычно использовать switch
над if
/else if
, если вы имеете дело с числовыми или перечисляемыми типами.
У коммутатора есть одно преимущество, когда дело доходит до ясности:
switch (i) {
case 1:
// do something
break;
case 2:
case 4:
// do something
break;
case 5:
// do something
break;
}
Если код для 2 и 4 идентичен, он может быть более понятным, чем:
if ( i == 1 ) {
// do something
}
if ( (i == 2) || (i == 4) ) {
// do something
}
if ( (i == 5 ) {
// do something
}
Вам также легче (или другому программисту) разделить случаи 2 и 4.
Я использую операторы switch для перечислений, более читаемо, если if if if if else if. Однако вы должны стараться избегать таких проверок в дизайне OO.
Вы используете оператор switch, когда вы включаете разные значения типов примитивов /enum/wrapper. (И не все типы примитивов/оболочек, только поддерживаемые - байты, короткие, char, int).
Если/else обрабатывает остальные.
Например, более эстетично говорить:
int i = getValueOfI();
switch (i) {
case 1:
// do something
break;
case 2:
// do something
break;
и др.
чем
if (i == 1) {
} else if (i == 2) {
}...
для большого числа случаев. Но вы не можете включать строки, значения функций или сложные условия, поэтому, если вам нужно что-то сделать, тогда вы застряли с if/else.
Лично я использую, чтобы найти обе конструкции слишком процедурные. Хотя это может рассматриваться как экзистентность OO, я использую для использования карты, содержащей экземпляры внутреннего интерфейса для каждого случая if. Думаю, это позволяет улучшить изоляцию кода. Однако, чтобы trully ответить на ваш вопрос, я использую только переключатели, когда у меня есть случаи, которые действительно перекрываются (я не использую инструкции break). К сожалению, это действительно не поддерживаемый кодовый блок.
Я бы предложил простое правило:
Всегда используйте switch
, если у вас есть как минимум 2 варианта, чтобы различать, когда тип данных можно использовать для коммутатора, и когда все параметры имеют постоянные значения.
Есть три веские причины. Один, в большинстве случаев switch
быстрее, чем каскад if
/else
. Во-вторых, это делает код более понятным. Три, о так забытая дилемма break
- это меньшее зло, чем огромные каскады if
/else
, которые случайно ломаются, потому что кто-то забыл else
.
Java-виртуальные машины фактически поддерживают два разных типа коммутатора: команду tableswitch и инструкцию lookupswitch. Команда tableswitch генерируется компилятором, если все константы case
лежат в узком диапазоне, в противном случае он генерирует поисковый переключатель. Для больших операторов switch
со многими случаями таблицаwitch более эффективна, чем lookupswitch. Lookupswitch обычно реализуется в виде бинарного поиска.
Есть два фактора для меня:
Считываемость и хотите ли вы решить что-то, используя диапазоны значений или условий (в операторах switch вы можете использовать только одно целое или перечислимое значение).
прежде всего, оператор switch должен использоваться. Если переключатель основан на значении переменной, то он может использоваться. Если он основан на сложном логическом выражении AND/OR/NOT, которое меняется для каждого условия, то вы не можете использовать его вообще.
При этом, если это применимо, и есть как минимум 2 случая, я пользуюсь коммутатором. Это более легко расширяемое, удобное для чтения и проверки.
Переключение и перечисление.
Если значение перечисления, которое вы тестируете, может быть законным null
по какой-либо причине, помещая его в оператор switch, генерирует исключение NullPointerException. Не смотря на байт-код, это несколько озадачивает, что он это сделает.
Объяснение: перечисления представляют собой синтаксический сахар, введенный в 1.5. Оператор switch по-прежнему работает с хорошими именами, но значения, которые он использует, являются ординалами, назначенными для перечисления. Чтобы получить порядковый номер, значение перечисления ДОЛЖНО быть не нулевым.
if statement
, с другой стороны, будет рад принять null
за значение перечисления и просто пропустить тест без NPE.
Может быть, немного оффтопик, но если бы я ответил только на вопрос в названии, я бы сказал, что вы не должны использовать переключатель во всех ситуациях, когда случаи представляют состояния какого-либо объекта. В этих случаях шаблон состояния гораздо более приятный.
Я всегда обнаружил, что оператор java-переключателя не настолько мощный, насколько мне нужно. В своем последний выпуск lambdaj реализует его с помощью умного использование укупорочного средства и приспособления Hamcrest.
Например, lambdaj Switcher позволяет реализовать шаблон стратегии. Предположим, вам нужно переключиться между тремя алгоритмами сортировки, основанными на некоторой характеристике сортируемого списка. В частности, предположим, что у нас есть алгоритм, специализированный для строк:
public List<String> sortStrings(List<String> list) {
// a sort algorithm suitable for Strings
}
другой, который хорошо работает с небольшими списками, имеющими не более 100 элементов:
public List<T> sortSmallList(List<T> list) {
// a sort algorithm suitable for no more than 100 items
}
и более общего назначения:
public List<String> sort(List<String> list) {
// a generic sort algorithm
}
Учитывая эти 3 метода сортировки, можно создать стратегию, которая выбирает наиболее подходящий из них следующим декларативным способом:
Switcher<List<T>> sortStrategy = new Switcher<List<T>>()
.addCase(having(on(List.class).get(0), instanceOf(String.class))),
new Closure() {{ of(this).sortStrings(var(List.class)); }})
.addCase(having(on(List.class).size(), lessThan(100))),
new Closure() {{ of(this).sortSmallList(var(List.class)); }})
.setDefault(new Closure() {{ of(this).sort(var(List.class)); }});
и отсортировать список с использованием наилучшего доступного алгоритма, вызвав коммутатор:
List<T> sortedList = sortStrategy.exec(list, list);
Ответ зависит от того, что именно вы делаете, а также от распределения вариантов.
Если одно условие является доминантным, то if/тогда подходит.
if (i == 1){
//do something
}else if (i == 2){
// do something else
}
Если условия распределены равномерно, оптимизация в компиляторе обеспечит преимущество в производительности. Эта разница в производительности становится более выраженной по мере увеличения количества возможных вариантов.
switch (i) {
case 1:
// do something
break;
case 2:
// do something else
break;
....
case N:
// do yet something else
break;
}
Тем не менее, если производительность НЕ важна, пойдите, с которой вы когда-либо приближаетесь, вы предпочитаете как наиболее читаемый (поддерживаемый и простой для записи).
Если, с другой стороны, если ваш код находится в точке доступа, где производительность важна, вы должны пойти с коммутатором.
Для "чрезвычайно больших" условий пример Марио lambdaj-переключателя действительно классный и с осторожностью при инициализации приведет к очень высокой производительности. Это очень похоже на кодирование того, что генерирует оптимизатор. Я бы назвал "чрезвычайно большим", как если бы количество опций было большим или достаточно сложным, чтобы заставить его стоить все, что нужно, и стоит того, чтобы поддерживать путаницу, когда последующий разработчик пытается пройти через код. (Комментируйте свой код, почему у вас есть все это!).
Если у вас слишком много условий для проверки аналогичного типа, вы можете переключиться на переключатель.
Как и в случае с другими языками, такими как C или С++, операторы switch полезны, когда вы хотите сравнить данную переменную со списком возможных значений и выполнить действие в зависимости от этих значений. Это термины, чем выражения else.
Я согласен с ответом x4u.
Кроме того, еще не упоминается еще один экземпляр, где я думаю, что лучше использовать блоки if
- else
, а не switch
: когда тестируются дополнительные условия в каждом из блоков case
с блоками if
. Я вижу эту смесь все время в существующем коде.
Например, я натолкнулся на этот код с switch
на строку type
и затем проверяет вторую строку extension
с помощью if
в обоих операторах case
.
public abstract class Temp {
boolean valid;
public Temp() {
String type = getType();
String extension = getFilenameExtension();
switch(type) {
case "Image File": {
if(!".jpg".equals(extension) && !".png".equals(extension)) {
warnWrongImageFormat();
valid = false;
}
break;
}
case "Zip File": {
if(!".zip".equals(extension)) {
warnWrongZipFormat();
valid = false;
}
break;
}
default: {
valid = true;
break;
}
}
}
abstract String getType();
abstract String getFilenameExtension();
abstract void warnWrongImageFormat();
abstract void warnWrongZipFormat();
}
Вместо этого гораздо проще и менее сложно уменьшить это до одного if
else
public abstract class Temp {
boolean valid;
public Temp() {
String type = getType();
String extension = getFilenameExtension();
valid = true;
if("Image File".equals(type) && !".jpg".equals(extension) && !".png".equals(extension)) {
warnWrongImageFormat();
valid = false;
}
else if("Zip File".equals(type) && !".zip".equals(extension)) {
warnWrongZipFormat();
valid = false;
}
}
abstract String getType();
abstract String getFilenameExtension();
abstract void warnWrongImageFormat();
abstract void warnWrongZipFormat();
}
Коммутатор имеет два недостатка:
Часто переключатель является признаком плохого дизайна OO, потому что вам лучше использовать полиморфизм.
Единственным возможным преимуществом коммутатора является то, что оно более читаемо. Но
switch (i) {
case 1:
// do something
break;
case 2:
// do something
break;
}
более читаемый, чем это:
if (i == 1)
//do something
else if (i == 2)
// do something else
Я бы сказал: нет! И у вас не было бы недостатков переключателя.
Мое предложение: постарайтесь избежать переключения.