Ключевое слово goto появилось одновременно с языками программирования. Действительно, безусловный переход заложил основы принятия решений в языке ассемблера: «если условие А, перейти туда, а иначе перейти сюда». Если вам доводилось читать код на ассемблере, который генерируют фактически все компиляторы, наверняка вы замечали многочисленные переходы, управляющие выполнением программы (компилятор Java производит свой собственный «ассемблерный» код, но последний выполняется виртуальной-машинойJava, а не аппаратным процессором). Команда goto реализует безусловный переход на уровне исходного текста программы, и именно это обстоятельство принесло ей дурную славу. Если программа постоянно «прыгает» из одного места в другое, нет ли способа реорганизовать ее код так, чтобы управление программой перестало быть таким «прыгучим»? Команда goto впала в настоящую немилость с опубликованием знаменитой статьи Эдгара Дейкстры «Команда GOTO вредна» (Goto considered harmful), их тех пор порицание команды goto стало чуть ли не спортом, а защитники репутации многострадального оператора разбежались по укромным углам. Как всегда в ситуациях такого рода, существует «золотая середина». Проблема состоит не в использовании goto вообще, но в злоупотреблении — все же иногда именно оператор goto позволяет лучше всего организовать управление программой. Хотя слово goto зарезервировано в языке Java, оно там не используется; Java не имеет команды goto. Однако существует механизм, чем-то похожий на безусловный переход и осуществляемый командами break и continue. Скорее, это способ прервать итерацию цикла, а не передать управление в другую точку программы. Причина его обсуждения вместе с goto состоит в том, что он использует тот же механизм — метки.
Метка
Метка представляет собой идентификатор с последующим двоеточием:
label1:
Единственное место, где в Java метка может оказаться полезной, — прямо перед телом цикла. Причем никаких дополнительных команд между меткой и телом цикла быть не должно. Причина помещения метки перед телом цикла может быть лишь одна — вложение внутри цикла другого цикла или конструкции выбора. Обычные версии break и continue прерывают только текущий цикл, в то время как их версии с метками способны досрочно завершать циклы и передавать выполнение в точку, адресуемую меткой:
label1:
внешний-цикл {
внутренний-цикл {
// ...
break; // 1
// ...
continue; // 2
//...
continue label1; // З
//...
break label1; // 4
}
}
В первом случае (1) команда break прерывает выполнение внутреннего цикла, и управление переходит к внешнему циклу. Во втором случае (2) оператор continue передает управление к началу внутреннего цикла. Но в третьем варианте (3) команда continue label1 влечет выход из внутреннего и внешнего циклов и возврат к метке label1. Далее выполнение цикла фактически продолжается, но с внешнего цикла. В четвертом случае (4) команда break label1 также вызывает переход к метке label1, но на этот раз повторный вход в итерацию не происходит. Это действие останавливает выполнение обоих циклов. Пример использования цикла for с метками:
//: control/LabeledFor.java
// Цикл for с метками
import static net.mindview.util.Print.*;
public class LabeledFor {
public static void main(String[] args) {
int i = 0;
outer: // Другие команды недопустимы
for(; true ;) { // infinite loop
inner: // Другие команды недопустимы
for(; i < 10; i++) {
print("i = " + i);
if(i == 2) {
print("continue");
continue;
}
if(i == 3) {
print("break");
i++; // В противном случае значение і
// не увеличивается.
break;
}
if(i == 7) {
print("continue outer");
i++; // В противном случае значение і
// не увеличивается.
continue outer;
}
if(i == 8) {
print("break outer");
break outer;
}
for(int k = 0; k < 5; k++) {
if(k == 3) {
print("continue inner");
continue inner;
}
}
}
}
// Использовать break или continue
// с метками, здесь не разрешается
}
}
<spoiler text="Output:">
i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
</spoiler> Заметьте, что оператор break завершает цикл for, вследствие этого выражение с инкрементом не выполняется до завершения очередного шага. Поэтому из-за пропуска операции инкремента в цикле переменная непосредственно увеличивается на единицу, когда і == 3. При выполнении условия і == 7 команда continue outer переводит выполнение на начало цикла; инкремент опять пропускается, поэтому и в этом случае переменная увеличивается явно.
Без команды break outer программе не удалось бы покинуть внешний цикл из внутреннего цикла, так как команда break сама по себе завершает выполнение только текущего цикла (это справедливо и для continue).
Конечно, если завершение цикла также приводит к завершению работы метода, можно просто применить команду return.
Теперь рассмотрим пример, в котором используются команды break и continue с метками в цикле while:
//: control/LabeledWhile.java
// Цикл while с метками
import static net.mindview.util.Print.*;
public class LabeledWhile {
public static void main(String[] args) {
int i = 0;
outer:
while(true) {
print("Внешний цикл while");
while(true) {
i++;
print("i = " + i);
if(i == 1) {
print("continue");
continue;
}
if(i == 3) {
print("continue outer");
continue outer;
}
if(i == 5) {
print("break");
break;
}
if(i == 7) {
print("break outer");
break outer;
}
}
}
}
}
<spoiler text="Output:">
Внешний цикл while
i = 1
continue
i = 2
i = 3
continue outer
Внешний цикл while
i = 4
i = 5
break
Внешний цикл while
i = 6
i = 7
break outer
</spoiler> Те же правила верны и для цикла while:
· Обычная команда continue переводит исполнение к началу текущего внутреннего цикла, программа продолжает работу.
· Команда continue с меткой вызывает переход к метке и повторный вход в цикл, следующий прямо за этой меткой.
· Команда break завершает выполнение текущего цикла.
· Команда break с меткой завершает выполнение внутреннего цикла и цикла, который находится после указанной метки.
Важно помнить, что единственная причина для существования меток в Java — наличие вложенных циклов и необходимость выхода по break и продолжения по continue не только для внутренних, но и для внешних циклов.
В статье Дейкстры особенно критикуются метки, а не сам оператор goto. Дейкстра отмечает, что, как правило, количество ошибок в программе растет с увеличением количества меток в этой программе. Метки затрудняют анализ программного кода. Заметьте, что метки Javaне страдают этими пороками, потому что их место расположения ограничено и они не могут использоваться для беспорядочной передачи управления. В данном случае от ограничения возможностей функциональность языка только выигрывает.
switch
Команду switch часто называют командой выбора. С помощью конструкции switch осуществляется выбор из нескольких альтернатив, в зависимости от значения целочисленного выражения. Форма команды выглядит так:
switch(целочисленное-выражение) {
case целое-значение1 : команда; break;
case целое-значение2 : команда; break;
case целое-значение3 : команда; break;
case целое-значение4 : команда; break;
case целое-значениеб : команда; break; // ..
default: оператор;
}
Целочисленное-выражение — выражение, в результате вычисления которого получается целое число. Команда switch сравнивает результат целочисленного-выражения с каждым последующим целым-значением. Если обнаруживается совпадение, исполняется соответствующая команда (простая или составная). Если же совпадения не находится, исполняется команда после ключевого слова default.
Нетрудно заметить, что каждая секция case заканчивается командой break, которая передает управление к концу команды switch. Такой синтаксис построения конструкции switch считается стандартным, но команда break не является строго обязательной.
Если она отсутствует, при выходе из секции будет выполняться код следующих секций case, пока в программе не встретится очередная команда break. Необходимость в подобном поведении возникает довольно редко, но опытному программисту оно может пригодиться.
Заметьте, что последняя секция default не содержит команды break; выполнение продолжается в конце конструкции switch, то есть там, где оно оказалось бы после вызова break. Впрочем, вы можете использовать break и в предложении default, без практической пользы, просто ради «единства стиля».
Команда switch обеспечивает компактный синтаксис реализации множественного выбора (то есть выбора из нескольких путей выполнения программы), но для нее необходимо управляющее выражение, результатом которого является целочисленное значение, такое как intили char. Если, например, критерием выбора является строка или вещественное число, то команда switch не подойдет. Придется использовать серию команд if-else.
Следующий пример случайным образом генерирует английские буквы. Программа определяет, гласные они или согласные:
//: control/VowelsAndConsonants.java
// Демонстрация конструкции switch
import java.util.*;
import static net.mindview.util.Print.*;
public class VowelsAndConsonants {
public static void main(String[] args) {
Random rand = new Random(47);
for(int i = 0; i < 100; i++) {
int c = rand.nextInt(26) + 'a';
printnb((char)c + ", " + c + ": ");
switch(c) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u': print("vowel");
break;
case 'y':
case 'w': print("Sometimes a vowel");
break;
default: print("consonant");
}
}
}
}
<spoiler text="Output:">
y, 121: Sometimes a vowel
n, 110: consonant
z, 122: consonant
b, 98: consonant
r, 114: consonant
n, 110: consonant
y, 121: Sometimes a vowel
g, 103: consonant
c, 99: consonant
</spoiler> Так как метод Random.nextInt(26) генерирует значение между 0 и 26, для получения символа нижнего регистра остается прибавить смещение 'а'. Символы в апострофах в секциях case также представляют собой целочисленные значения, используемые для сравнения.
Обратите внимание на «стопки» секций case, обеспечивающие возможность множественного сравнения для одной части кода. Будьте начеку и не забывайте добавлять команду break после каждой секции case, иначе программа просто перейдет к выполнению следующей секции case. В команде
int с = rand.nextInt(26) + 'а';
метод rand.nextInt() выдает случайное число int от 0 до 25, к которому затем прибавляется значение 'а'. Это означает, что символ а автоматически преобразуется к типу int для выполнения сложения. Чтобы вывести с в символьном виде, его необходимо преобразовать к типуchar; в противном случае значение будет выведено в числовом виде.
Резюме
В этой главе завершается описание основных конструкций, присутствующих почти во всех языках программирования: вычислений, приоритета операторов, приведения типов, условных конструкций и циклов. Теперь можно сделать следующий шаг на пути к миру объектно-ориентированного программирования. Следующая глава ответит на важные вопросы об инициализации объектов и завершении их жизненного цикла, после чего мы перейдем к важнейшей концепции сокрытия реализации.