Метки в Java
Используйте метки в Java для выхода из внешних циклов или перехода к следующей итерации внешнего цикла изнутри вложенных циклов.
Обычный break завершает выполнение ближайшего цикла, а обычный continue пропускает одну его итерацию. Когда вы находитесь внутри вложенных циклов и нужно управлять внешним циклом изнутри внутреннего, Java предоставляет метки: имя, которое можно присвоить циклу, и оператор break или continue, обращающийся к нему по этому имени.
В этой главе рассматривается, как объявлять метку, как метки меняют поток выполнения при использовании break и continue, когда (редко) их стоит применять, и какие более чистые альтернативы следует рассматривать в первую очередь.
Это наиболее близкий аналог goto в Java — и он намеренно ограничен только циклами (и switch).
Объявление метки
Метка — это идентификатор, за которым следует двоеточие, размещённый непосредственно перед циклом:
outer:
for (int i = 0; i < 5; i++) {
// ...
}Имя (outer в данном случае) подчиняется тем же правилам, что и любой идентификатор Java. Называть его outer необязательно — подойдёт любое допустимое имя: searchLoop, rows и т. д. Выбирайте имя, которое отражает назначение метки.
Метка с break
break <label>; завершает цикл, помеченный данной меткой, независимо от глубины вложенности:
int[][] grid = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int target = 5;
int foundRow = -1, foundCol = -1;
search:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == target) {
foundRow = r;
foundCol = c;
break search;
}
}
}
System.out.println("found at " + foundRow + "," + foundCol);Без метки с break пришлось бы использовать переменную-флаг или переструктурировать код. С меткой оба цикла завершаются сразу в момент обнаружения искомого значения.
Метка с continue
continue <label>; пропускает оставшуюся часть текущей итерации внутреннего цикла и переходит к следующей итерации помеченного внешнего цикла:
rowLoop:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] < 0) {
continue rowLoop; // skip the rest of this row
}
process(grid[r][c]);
}
}Когда внутренний цикл встречает отрицательное значение, оставшаяся часть текущей строки пропускается и выполнение переходит к следующей строке.
Используйте метки экономно
Метки работают, но ими легко злоупотребить. Прежде чем прибегнуть к ним, рассмотрите альтернативы:
-
Вынесите код во вспомогательный метод. Внутри метода обычный
returnзавершает все циклы разом и, как правило, читается яснее:static int[] find(int[][] grid, int target) { for (int r = 0; r < grid.length; r++) { for (int c = 0; c < grid[r].length; c++) { if (grid[r][c] == target) return new int[]{r, c}; } } return null; } -
Используйте флаг — чуть менее лаконично, но явно и без
goto:boolean found = false; for (int r = 0; !found && r < grid.length; r++) { for (int c = 0; c < grid[r].length; c++) { if (grid[r][c] == target) { found = true; break; } } } -
Используйте потоки (streams) для задач фильтрации вместо вложенных циклов.
Прибегайте к меткам тогда, когда альтернативы действительно ухудшают читаемость — как правило, при двух-трёх уровнях вложенности с очевидным намерением «прервать поиск» или «перейти к следующей итерации внешнего цикла».
Метки и switch
Метки работают и с switch, хотя это редкость:
sw:
switch (cmd) {
case "x":
if (someCondition) break sw;
// ...
break;
}На практике это никогда не добавляет ясности по сравнению с обычным break.
Практический пример
Что дальше
Вы завершили главы о потоке управления: условные операторы, тернарный оператор, switch, циклы и операторы их прерывания. Следующая часть посвящена методам — упаковке блоков кода для вызова по имени.