15

15


Традиционные процедурные языки (такие, как С) требовали, чтобы все пе¬ременные определялись в начале блока цикла, чтобы компилятор при создании блока мог выделить память под эти переменные. В Java и С++ переменные раз¬решено объявлять в том месте блока цикла, где это необходимо. Это позволяет программировать в более удобном стиле и упрощает понимание кода.
Оператор-запятая
Ранее в этой главе уже упоминалось о том, что оператор «запятая» (но не запя- тая-разделитель, которая разграничивает определения и аргументы функций) может использоваться в Java только в управляющем выражении цикла for. И в секции инициализации цикла, и в его управляющем выражении можно за¬писать несколько команд, разделенных запятыми; они будут обработаны после¬довательно.
Оператор «запятая» позволяет определить несколько переменных в цикле for, но все эти переменные должны принадлежать к одному типу:
//. control/CommaOperator.java
public class CommaOperator {
public static void main(String[] args) {
for(int i = 1. j = i + 10, i < 5. i++, j = i * 2) {
System out.printlnC'i = " + i + " j = " + j);
}
}
} /* Output: i = 1 j = 11 i = 2 j = 4 i = 3 j = 6 i = 4 j = 8 *///:-
Определение int в заголовке for относится как к i, так и к j. Инициализацон- ная часть может содержать любое количество определений переменных одного типа. Определение переменных в управляющих выражениях возможно только в цикле for. На другие команды выбора или циклов этот подход не распростра¬няется.
Синтаксис foreach
В Java SE5 появилась новая, более компактная форма for для перебора элемен¬тов массивов и контейнеров (см. далее). Эта упрощенная форма, называемая синтаксисом foreach, не требует ручного изменения служебной переменной для перебора последовательности объектов — цикл автоматически представляет очередной элемент.
Следующая программа создает массив float, после чего перебирает все его элементы:
//• control/ForEachFloat.java import java util.*,
public class ForEachFloat {
public static void main(String[] args) { Random rand = new Random(47), float f[] = new float[10], for(int i = 0; i < 10. i++)
f[i] = rand.nextFloatO, for(float x f)
System out println(x).
}
} /* Output
0.72711575
0.39982635
0.5309454
0.0534122
0.16020656
0.57799757
0.18847865
0.4170137
0.51660204
0.73734957 *///.-
Массив заполняется уже знакомым циклом for, потому что для его заполне¬ния должны использоваться индексы. Упрощенный синтаксис используется в следующей команде:
for(float X f)
Эта конструкция определяет переменную х типа float, после чего последова¬тельно присваивает ей элементы f.
Любой метод, возвращающий массив, может использоваться с данной разно¬видностью for. Например, класс String содержит метод toCharArray(), возвращаю¬щий массив char; следовательно, перебор символов строки может осуществлять¬ся так:
//: control/ForEachString.java
public class ForEachString {
public static void main(String[] args) {
for(char с : "An African Swallow".toCharArray() ) System.out.print(c + " ");
}
} /* Output:
An African Swallow *///.-
Как будет показано далее, «синтаксис foreach» также работает для любого объекта, поддерживающего интерфейс Iterable.
Многие команды for основаны на переборе серии целочисленных значений:
for (int i = 0; i < 100; i++)
В таких случаях «синтаксис foreach» работать не будет, если только вы пред¬варительно не создадите массив int. Для упрощения этой задачи я включил в библиотеку net.mindview.util.Range метод range(), который автоматически гене¬рирует соответствующий массив:
//: control/ForEachlnt.java
import static net.mindview.util.Range.*,
import static net.mindview.util Print.*;
public class ForEachlnt {
public static void main(String[] args) { for(int i : range(10)) // 0..9
printnbCi + „ v).
printO;
for(int i : range(5, 10)) // 5..9
printnb(i +
printO;
for(int i : range(5. 20. 3)) // 5
printnbCi +
printO:
}
} /* Output: 0 12 3 4 5 6 7 8 9 5 6 7 8 9 5 8 11 14 17 *///:-
Обратите внимание на использование printnb() вместо print(). Метод printnb() не выводит символ новой строки, что позовляет построить строку по фраг¬ментам.
return
Следующая группа ключевых слов обеспечивает безусловный переход, то есть передачу управления без проверки каких-либо условий. К их числу относятся команды return, break и continue, а также конструкция перехода по метке, анало¬гичная goto в других языках.
У ключевого слова return имеется два предназначения: оно указывает, какое значение возвращается методом (если только он не возвращает тип void), а так¬же используется для немедленного выхода из метода. Метод test() из предыду¬щего примера можно переписать так, чтобы он воспользовался новыми возмож¬ностями:
//: control/IfElse2.java
import static net.mindview.util.Print.*:
public class IfElse2 {
static int test(int testval. int target) { if(testval <@062> target)
return +1; else if(testval < target) return -1;
else
return 0; // Одинаковые значения
}
public static void main(String[] args) { prmt(test(10. 5)); print(test(5. 10)): print(test(5. 5));
}
} /* Output: 1 -1 0
В данном случае секция else не нужна, поскольку работа метода не продол¬жается после выполнения инструкции return.
Если метод, возвращающий void, не содержит команды return, такая команда неявно выполняется в конце метода. Тем не менее, если метод возвращает лю¬бой тип, кроме void, проследите за тем, чтобы каждая логическая ветвь возвра¬щала конкретное значение.
break и continue
В теле любого из циклов вы можете управлять потоком программы, используя специальные ключевые слова break и continue. Команда break завершает цикл, при этом оставшиеся операторы цикла не выполняются. Команда continue оста¬навливает выполнение текущей итерации цикла и переходит к началу цикла, чтобы начать выполнение нового шага.
Следующая программа показывает пример использования команд break и con¬tinue внутри циклов for и while:
//: control/BreakAndContinue.java // Применение ключевых слов break и continue import static net.mindview.util.Range.*;
public class BreakAndContinue {
public static void main(String[] args) { for(int i = 0: i < 100; i++) {
if(i == 74) break; // Выход из цикла
if(i % 9 != 0) continue; // Следующая итерация
System.out print(i + " ");
}
System out.printlnO; // Использование foreach: for(int i : range(lOO)) {
if(i == 74) break; // Выход из цикла
if(i % 9 != 0) continue; // Следующая итерация
System.out.print(i + " ");
}
System.out.println(); int i = 0;
// "Бесконечный цикл": while(true) { i++;
int j = i * 27,
if(j == 1269) break; // Выход из цикла
if(i % 10 != 0) continue; 11 Возврат в начало цикла
System.out.print(i + " ");
}
}
} /* Output:
0 9 18 27 36 45 54 63 72 0 9 18 27 36 45 54 63 72 10 20 30 40 *///:-
В цикле for переменная i никогда не достигает значения 100 — команда break прерывает цикл, когда значение переменной становится равным 74. Обычно break используется только тогда, когда вы точно знаете, что условие выхода из цикла действительно достигнуто. Команда continue переводит исполнение в на¬чало цикла (и таким образом увеличивает значение i), когда i не делится без ос¬татка на 9. Если деление производится без остатка, значение выводится на эк¬ран.
Второй цикл for демонстрирует использование «синтаксиса foreach» с тем же результатом.
Последняя часть программы демонстрирует «бесконечный цикл», который теоретически должен исполняться вечно. Однако в теле цикла вызывается ко¬манда break, которая и завершает цикл. Команда continue переводит исполнение к началу цикла, и при этом остаток цикла не выполняется. (Таким образом, вы¬вод на экран в последнем цикле происходит только в том случае, если значе¬ние i делится на 10 без остатка.) Значение 0 выводится, так как 0 % 9 дает в ре¬зультате 0.
Вторая форма бесконечного цикла — for(;;). Компилятор реализует конст¬рукции while(true) и for(;;) одинаково, так что выбор является делом вкуса.
Нехорошая команда goto
Ключевое слово goto появилось одновременно с языками программирования. Действительно, безусловный переход заложил основы принятия решений в языке ассемблера: «если условие А, перейти туда, а иначе перейти сюда». Если вам доводилось читать код на ассемблере, который генерируют фактически все компиляторы, наверняка вы замечали многочисленные переходы, управ¬ляющие выполнением программы (компилятор Java производит свой собствен¬ный «ассемблерный» код, но последний выполняется виртуальной-машиной Java, а не аппаратным процессором).
Команда goto реализует безусловный переход на уровне исходного текста программы, и именно это обстоятельство принесло ей дурную славу. Если программа постоянно «прыгает» из одного места в другое, нет ли способа реор¬ганизовать ее код так, чтобы управление программой перестало быть таким «прыгучим»? Команда goto впала в настоящую немилость с опубликованием знаменитой статьи Эдгара Дейкстры «Команда GOTO вредна» (Goto considered harmful), их тех пор порицание команды goto стало чуть ли не спортом, а за¬щитники репутации многострадального оператора разбежались по укромным углам.
Как всегда в ситуациях такого рода, существует «золотая середина». Про¬блема состоит не в использовании goto вообще, но в злоупотреблении — все же иногда именно оператор goto позволяет лучше всего организовать управление программой.
1 Оригинал статьи Go То Statement considered harmful имеет постоянный адрес в Интернете: http://www.acm.org/classics/oct95. — Примеч. ред.
Хотя слово goto зарезервировано в языке Java, оно там не используется; Java не имеет команды goto. Однако существует механизм, чем-то похожий на безус¬ловный переход и осуществляемый командами break и continue. Скорее, это способ прервать итерацию цикла, а не передать управление в другую точку про¬граммы. Причина его обсуждения вместе с goto состоит в том, что он использу¬ет тот же механизм — метки.
Метка представляет собой идентификатор с последующим двоеточием:
label 1•
Единственное место, где в Java метка может оказаться полезной, — прямо пе¬ред телом цикла. Причем никаких дополнительных команд между меткой и те¬лом цикла быть не должно. Причина помещения метки перед телом цикла мо¬жет быть лишь одна — вложение внутри цикла другого цикла или конструкции выбора. Обычные версии break и continue прерывают только текущий цикл, в то время как их версии с метками способны досрочно завершать циклы и пе¬редавать выполнение в точку, адресуемую меткой:
labell:
внешний-цикл {
внутренний-цикл { //.
break; // 1 // .
continue; // 2 //..
continue labell; // 3 //...
break labell; // 4

В первом случае (1) команда break прерывает выполнение внутреннего цикла, и управление переходит к внешнему циклу. Во втором случае (2) опера¬тор continue передает управление к началу внутреннего цикла. Но в третьем ва¬рианте (3) команда continue labell влечет выход из внутреннего и внешнего цик¬лов и возврат к метке labell. Далее выполнение цикла фактически продолжается, но с внешнего цикла. В четвертом случае (4) команда break labell также вызыва¬ет переход к метке labell, но на этот раз повторный вход в итерацию не проис¬ходит. Это действие останавливает выполнение обоих циклов. Пример использования цикла 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++) {
printC'i = " + i);
if(i == 2) {
print("continue"); continue;
}
if(i == 3) {
printC'break");
i++; // В противном случае значение i
// не увеличивается break;
}
if(i - 7) {
printC'continue outer");
i++; // В противном случае значение i
// не увеличивается continue outer;
}
if(i == 8) {
printC'break outer"); break outer;
}
for(int k = 0; k < 5; k++) {• if (k == 3) {
printC'continue inner"); continue inner;
}
}
}
}
// Использовать break или continue // с метками здесь не разрешается
}
} /* 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
break outer *///:-
Заметьте, что оператор break завершает цикл for, вследствие этого выраже¬ние с инкрементом не выполняется до завершения очередного шага. Поэтому из-за пропуска операции инкремента в цикле переменная непосредственно уве¬личивается на единицу, когда i — 3. При выполнении условия i == 7 команда continue outer переводит выполнение на начало цикла; инкремент опять пропус¬кается, поэтому и в этом случае переменная увеличивается явно.

Report Page