W3docs

Метки в 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.

Практический пример

java— editable, runs on the server

Что дальше

Вы завершили главы о потоке управления: условные операторы, тернарный оператор, switch, циклы и операторы их прерывания. Следующая часть посвящена методам — упаковке блоков кода для вызова по имени.

Практика

Практика
Что делает break с меткой?
Что делает break с меткой?
Was this page helpful?