Операторы перехода в Java это return, break и continue: они прерывают линейное выполнение программы и передают управление в другое место. return завершает метод и может вернуть значение, break завершает switch или цикл, continue пропускает текущую итерацию цикла; у break и continue есть форма с меткой для выхода из вложенных циклов. Слово goto в Java зарезервировано, но самого оператора goto в языке нет (JLS, раздел 14.7). Ниже: синтаксис и примеры каждого оператора, метки, замена меток на return и почему goto так и не появился.

Кратко

  • Операторов перехода три: return, break, continue. Слово goto зарезервировано вместе с const, но не используется.
  • return завершает метод: в методе с типом результата он обязан вернуть значение на каждом пути выполнения, в void-методе допустим только return; без значения.
  • break без метки завершает ближайший switch или цикл; break label; завершает помеченный внешний цикл целиком.
  • continue без метки пропускает остаток текущей итерации ближайшего цикла; continue label; переходит к следующей итерации помеченного внешнего цикла.
  • Метка это имя с двоеточием перед оператором. Поэтому строка https://www.google.com/ внутри метода компилируется: https: это метка, а все после // это комментарий.
  • Чем меньше меток и вложенных циклов, тем читаемее код: вложенный цикл лучше вынести в метод и выйти через return. С Java 14 switch со стрелкой -> не требует break, а yield отдает значение из switch-выражения.
Сегодня поговорим об операторах перехода в языке Java:
  • return
  • break
  • continue
  • goto
Для начала определимся с тем, что это вообще такое. Как известно, в обычной ситуации программа выполняется линейно: сверху вниз, команда за командой. Линейный ход программы могут изменить так называемые управляющие конструкции: например, ветвления (if) и циклы (for, while и т. д.). Помимо управляющих конструкций, линейное выполнение программы могут изменить операторы перехода. Они отвечают за перенаправление выполнения программы в определенное место, которое зависит от контекста и конкретного оператора.Мем с Кермитом и его темным двойником в капюшоне: «Переходи на темную сторону», шутка про операторы переходаДавай рассмотрим каждый из четырех операторов повнимательнее.

return

Именно с этим оператором чаще всего в первую очередь знакомятся новички. Оператор return завершает работу метода, в котором его вызвали, и выполнение программы возвращается туда, откуда данный метод вызван. У return есть две формы:
  1. Немедленно заканчивает выполнение метода.
  2. Немедленно заканчивает выполнение метода и возвращает какое-то значение в качестве результата работы метода.
Синтаксис обеих форм:
return;
return value; // где value это некоторое возвращаемое значение
В методах, которые возвращают некоторое значение, обязателен как минимум один оператор return с возвращаемым значением, который гарантированно вызовется, и недопустимо наличие оператора return без возвращаемого значения. Рассмотрим примеры ниже:
public int sum(int a, int b) {
    return a + b;
}

public String getGreetings(String name) {
    return "Hello " + name;
}

public int max(int x, int y) {
    if (x > y) {
        return x;
    } else {
        return y;
    }
}
В методах, которые не возвращают значений (методы void) допустимо, но не обязательно, наличие как минимум одного оператора return без возвращаемого значения, и недопустим ни один оператор return с возвращаемым значением. Рассмотрим это на примерах ниже:
public void print(String s) {
    // наличие return в void методах не обязательно
    System.out.println(s);
}

//Метод выведет в консоль число, если оно нечетное
public void printIfOdd(int number) {
    if (number % 2 == 0) {
        // Если число четное, метод завершит свою работу
        // Наличие return в void методах опционально
        return;
    }

    System.out.println(number);
}

// Метод выведет в консоль наибольшее значение из массива
private void printMaxInArray(int[] array) {
    if (array == null || array.length == 0) {
        /*
         Если массив пуст, метод завершит свою работу.
         Иногда полезно проверять подобным образом аргументы метода вначале и прерывать выполнение метода, если аргументы не подходят для дальнейшей корректной работы
        */
        System.out.println("Empty array");
        return;
    }

    int max = array[0];
    for (int i = 1; i < array.length; i++) {
        if (array[i] > max) {
            max = array[i];
        }
    }
    System.out.println(max);
}

labels (метки)

Прежде чем рассматривать операторы break и continue, хотелось бы поговорить о метках в ЯП Java. Это важно, потому что в некоторых ситуациях операторы break и continue используются совместно с метками. Но для начала попробуйте ответить на вопрос, скомпилируется ли такой код:
public static void main(String[] args) {
    https://www.google.com/
    System.out.println("Interesting...");
}
Метка это именованная часть кода. Сама по себе метка не несет никакой функциональности. Это что-то вроде закладки в коде, которую программист намерен использовать впоследствии. Метка в коде определяется весьма просто: через имя и двоеточие. Например:
  • labelName:
  • outerLoop:
  • printing:
  • anyWordYouLike:
А вот так выглядят метки внутри Java-кода:
public static void main(String[] args) {
    definePrintName:
    System.out.println("Таблица Умножения");

    loop1:
    for (int i = 1; i <= 10; i++) {
        loop2:
        for (int j = 1; j <= 10; j++) {
            System.out.printf("%4d", i * j);
        }
        System.out.println();
    }
}
Вывод метода main будет следующим:
Таблица Умножения
   1   2   3   4   5   6   7   8   9   10
   2   4   6   8   10  12  14  16  18  20
   3   6   9   12  15  18  21  24  27  30
   4   8   12  16  20  24  28  32  36  40
   5   10  15  20  25  30  35  40  45  50
   6   12  18  24  30  36  42  48  54  60
   7   14  21  28  35  42  49  56  63  70
   8   16  24  32  40  48  56  64  72  80
   9   18  27  36  45  54  63  72  81  90
  10  20  30  40  50  60  70  80  90  100

Process finished with exit code 0
В примере выше definePrintName, loop1: и loop2: это метки. loop1: и loop2: “отмечают” два цикла: внешний и внутренний. Использование меток мы рассмотрим в разделе ниже. А пока, если вы ответили “нет” на вопрос скомпилируется ли такой код:
public static void main(String[] args) {
      https://www.google.com/
      System.out.println("Interesting...");
  }
Попробуйте ответить на него еще раз, с помощью IDE.

break

Оператор break используется в двух случаях:
  1. Для завершения какой-либо ветки выполнения в блоке switch-case.
  2. Для прерывания выполнения цикла.
У оператора есть две формы: с маркировкой (меткой) и без. Синтаксис обеих форм такой:
break labelName; // Синтаксис оператора с меткой
break; // Синтаксис оператора без метки

break в switch-case

В блоках switch-case оператор break используется без меток:
public static void main(String[] args) {
    int dayOfWeekInt = 4;
    String dayOfWeek;
    switch (dayOfWeekInt) {
        case 1:
            dayOfWeek = "Понедельник";
            break;
        case 2:
            dayOfWeek = "Вторник";
            break;
        case 3:
            dayOfWeek = "Среда";
            break;
        case 4:
            dayOfWeek = "Четверг";
            break;
        case 5:
            dayOfWeek = "Пятница";
            break;
        case 6:
            dayOfWeek = "Суббота";
            break;
        case 7:
            dayOfWeek = "Воскресенье";
            break;
        default:
            dayOfWeek = "Неизвестный день";
            break;
    }

    System.out.println("Сегодня " + dayOfWeek);
}
С Java 14 у switch есть вторая форма записи, со стрелкой: case 1 -> dayOfWeek = "Понедельник";. В ней выполнение не «проваливается» в следующую ветку, поэтому break не нужен. А если switch используется как выражение и сам возвращает значение, вместо break появился оператор yield: он отдает результат из блока ветки (JLS, раздел 14.21). Тот же пример с днями недели в виде switch-выражения:
public static void main(String[] args) {
    int dayOfWeekInt = 4;
    String dayOfWeek = switch (dayOfWeekInt) {
        case 1 -> "Понедельник";
        case 2 -> "Вторник";
        case 3 -> "Среда";
        case 4 -> "Четверг";
        case 5 -> "Пятница";
        case 6 -> "Суббота";
        case 7 -> "Воскресенье";
        default -> "Неизвестный день";
    };

    System.out.println("Сегодня " + dayOfWeek);
}
Вывод тот же: Сегодня Четверг, но без семи break и без риска забыть один из них.

break в циклах

В циклах оператор break используют для прерывания дальнейших итераций после того, как достигнуты определенные условия. Часто такое можно встретить, когда необходимо перебрать массив или коллекцию элементов и найти в ней какой-то элемент, удовлетворяющий нужным условиям. Рассмотрим такой пример. У нас есть массив и нам необходимо определить, содержит ли массив отрицательные элементы:
int a[] = {1,2,234,-123,12,-2,312,0,412,433};
boolean arrayHasNegativeElements = false;

for (int i = 0; i < a.length; i++) {
   if (a[i] < 0) {
       /*
        Как только найдется
        хотя бы один отрицательный элемент,
        мы прервем цикл с помощью
        оператора break, потому что
        мы выяснили то, что нас интересовало,
        и дальнейший перебор элементов не имеет смысла.
        */
       arrayHasNegativeElements = true;
       break;
   }
}
Рассмотрим тот же пример с различными циклами. Цикл for-each:
public static void main(String[] args) {
    int a[] = {1,2,234,-123,12,-2,312,0,412,433};
    boolean arrayHasNegativeElements = false;

    for (int number : a) {
        if (number < 0) {
            arrayHasNegativeElements = true;
            break;
        }
    }
}
Цикл while:
public static void main(String[] args) {
    int a[] = {1,2,234,-123,12,-2,312,0,412,433};
    boolean arrayHasNegativeElements = false;

    int counter = 0;
    while (counter < a.length) {
        if (a[counter] < 0) {
            arrayHasNegativeElements = true;
            break;
        }
        counter ++;
    }
}
Цикл do-while:
public static void main(String[] args) {
    int a[] = {1,2,234,-123,12,-2,312,0,412,433};
    boolean arrayHasNegativeElements = false;

    int counter = 0;
    do {
        if (a[counter] < 0) {
            arrayHasNegativeElements = true;
            break;
        }
        counter ++;
    } while (counter < a.length);
}
Еще одним примером оператора break в циклах является прерывание бесконечного цикла при достижении определенных условий. Приведем пример программы, выводящей строку, которую ввел пользователь до тех пор, пока юзер не введет слово “stop”:
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    String line;

    while (true) {
        line = scanner.nextLine();
        if ("stop".equals(line)){
            /*
             Прерываем бесконечный цикл,
             при достижении
             определенного условия
             */
            break;
        }
        System.out.println("Пользователь ввел: " + line);
    }
}

break с меткой: выход из вложенных циклов

Рассмотрим использование оператора break совместно с меткой. Прерывание с меткой используется в случаях с несколькими циклами, причем вложенными один в другой. В таком случае один из циклов (или же все циклы) помечается меткой. Далее оператор break совместно с указанием метки прерывает нужный цикл. Рассмотрим пример, в котором нам необходимо понять, есть ли отрицательный элемент, но только не в массиве, а в матрице:
public static void main(String[] args) {
   int[][] a = {
           {1, 2, 3},
           {-412, 12, 0},
           {1223, 474, -54}
   };

   boolean hasNegative = false;

   searchNegative:
       for (int i = 0; i < a.length; i++) {
           for (int j = 0; j < a[i].length; j++) {
               if (a[i][j] < 0) {
                   /*
                       Если использовать break без метки,
                       тогда прервется вложенный цикл for,
                       но внешний продолжит выполнять свои итерации
                       и поиск продолжится.

                       Поэтому мы "помечаем" внешний цикл меткой `searchNegative`
                       и прерываем внешний цикл оператором break совместно с нужной меткой.
                    */
                   hasNegative = true;
                   break searchNegative;
               }
           }
       }
}

continue

У оператора continue также есть две формы, с меткой и без:
continue; // форма оператора без метки
continue labelName; // форма оператора с меткой
В отличие от оператора break, который прерывает все оставшиеся итерации цикла, оператор continue прерывает текущую итерацию и приводит к запуску следующей.Мем «To be continued...» белыми буквами на черном фоне, шутка про оператор continueТакое может быть полезно, если нужно провести некоторые операции над элементами, которые удовлетворяют определенным условиям. Скажем, у нас есть строка, и мы хотим посчитать количество слов, начинающихся с буквы “м”:
public static void main(String[] args) {
    String sentence = "Мама мыла раму";
    String[] words = sentence.split(" ");

    int mWordsCount = 0;

    for (int i = 0; i < words.length; i++) {
        if ( ! words[i].toLowerCase().startsWith("м")) {
            /*
             Если слово не начинается с буквы м,
             то текущая итерация прервется и цикл
             ПРОДОЛЖИТ выполнение со следующей итерации
             */
            continue;
        }

        mWordsCount ++;
    }

    System.out.println("Кол-во слов, начинающихся с буквы М в предложении: " + "[" + sentence + "] = " + mWordsCount);
}
После выполнения данного кода будет такой вывод в консоли:
Кол-во слов, начинающихся с буквы М в предложении: [Мама мыла раму] = 2

continue с меткой

Оператор continue совместно с меткой также используется при переборе элементов. Представим себе матрицу, в которой нам нужно посчитать количество строк с отрицательными элементами:
public static void main(String[] args) {
    int[][] a = {
            {1, 23, -1, 23, -12},
            {21, 21, 0, 23, 123, 45},
            {123, 3},
            {123, -5, 4, -3},
            {-1, -2, -3}
    };

    int rowsWithNegativeElementsCount = 0;

    rowsLoop:
    // Проходим по каждой строке
        for (int[] arr : a) {
            for (int number : arr) {
                if (number < 0) {
                    /*
                     Если в текущей строке найдется
                     хотя бы 1 отрицательный элемент,
                     тогда мы увеличим переменную счетчик,
                     и с помощью оператора continue rowsLoop
                     прервем текущую итерацию внешнего цикла и
                     принудительно начнем следующую
                     */
                    rowsWithNegativeElementsCount ++;
                    continue rowsLoop;
                }
            }
        }

    System.out.println("Rows With Negative Elements Count = " + rowsWithNegativeElementsCount);
}
Выводом данного кода будет:
Rows With Negative Elements Count = 3

break, continue или return: что выбрать

Стоит сказать, что операторы break, continue и return можно по-разному использовать для достижения одной и той же функциональности. Так, можно переписать последний пример и вместо continue использовать break:
public static void main(String[] args) {
    int[][] a = {
            {1, 23, -1, 23, -12},
            {21, 21, 0, 23, 123, 45},
            {123, 3},
            {123, -5, 4, -3},
            {-1, -2, -3}
    };

    int rowsWithNegativeElementsCount = 0;

    for (int[] arr : a) {
        for (int number : arr) {
            if (number < 0) {
                rowsWithNegativeElementsCount ++;
                break;
            }
        }
    }

    System.out.println("Rows With Negative Elements Count = " + rowsWithNegativeElementsCount);
}
Разница между break и continue с меткой заключается в том, что break завершает итерации цикла, в котором он написан. А continue с меткой пропускает текущую итерацию цикла, отмеченного меткой. В некоторых ситуациях можно заменять одно на другое, и в функциональности программы все останется прежним. О том, что лучше выбрать (спойлер: читаемость кода), расскажем немного ниже. Оператор break можно заменить не только на continue с меткой, но и на return. Только перед этим нужно вынести вложенный цикл в отдельный метод:
public static void main(String[] args) {
    int[][] a = {
            {1, 23, -1, 23, -12},
            {21, 21, 0, 23, 123, 45},
            {123, 3},
            {123, -5, 4, -3},
            {-1, -2, -3}
    };

    int rowsWithNegativeElementsCount = 0;

    for (int[] arr : a) {
        if (arrayHasNegativeElements(arr)) {
            rowsWithNegativeElementsCount ++;
        }
    }

    System.out.println("Rows With Negative Elements Count = " + rowsWithNegativeElementsCount);
}

static boolean arrayHasNegativeElements(int[] array) {
    for (int number : array) {
        if (number < 0) {
            return true;
        }
    }

    return false;
}
Уйма способов написать одно и то же. Какой же выбрать? В промышленном программировании в этом вопросе решает легкость восприятия кода. Чем проще написано, тем лучше. Чем больше вложенных циклов, тем сложнее воспринимать код. Особенно если циклы отмечены различными метками, которые используются в прерываниях и продолжениях (break и continue). Если есть возможность не использовать метки, лучше так и делать. В ином случае старайтесь писать максимально понятно и красиво.

goto

В некоторых языках программирования существует оператор goto. Обычно он перенаправляет выполнение кода в какую-либо часть программы, помеченную меткой. Но в Java goto, можно сказать, и есть, и нет. Давайте разбираться. Список ключевых слов в Java включает слово goto. Однако данный оператор помечен как not used (не используется). Тут важно не путать язык и виртуальную машину: в байткоде JVM инструкция goto есть и сегодня (JVMS, раздел 6.5), именно в нее компилятор превращает циклы и ветвления. А вот в самом языке оператора goto никогда не было: спецификация прямо говорит, что, в отличие от C и C++, в Java его нет, а метки нужны только для break и continue (JLS, раздел 14.7). Одна из причин заключается в том, что блоки кода, содержащие оператор goto, читались не так хорошо, как блоки кода, выполняющие те же функции, но без goto, а с альтернативными подходами (break, continue, вынесение блока кода в методы). Были, собственно, и другие, например:
  • сложность чтения и понимания кода, в котором есть операторы goto;
  • усложнение оптимизации кода компилятору (а иногда и невозможность);
  • повышение вероятности создания трудно уловимых ошибок в коде.
Для многих не секрет, что в некоторых языках программирования оператор goto вполне успешно функционирует. Однако программисты избегают его использование. Почитать о причинах этого можно в одной статье на хабре. Но зачем тогда оставлять goto в списке зарезервированных слов? Спецификация языка отвечает прямо: слова const и goto зарезервированы, хотя и не используются, чтобы компилятор мог выдавать более понятные ошибки, когда эти ключевые слова из C++ по ошибке попадают в Java-код (JLS, раздел 3.9). Есть и побочный эффект: раз слово нельзя использовать как имя переменной, метода или класса, то, если оператор когда-нибудь вернут в язык, старый код от этого не сломается. Возможно когда-нибудь goto вернется в наши ряды, но вероятность этого невысока.

Сводная таблица операторов перехода

Все операторы перехода в одной таблице (правила из разделов 14.15 и 14.16 спецификации Java):
Оператор Без метки С меткой Где допустим
return завершает метод; в методе с типом результата обязан вернуть значение меток нет любой метод, конструктор, лямбда
break завершает ближайший switch или цикл завершает помеченный оператор: обычно внешний цикл, но подойдет и блок в фигурных скобках switch, циклы, помеченные операторы
continue пропускает остаток текущей итерации ближайшего цикла переходит к следующей итерации помеченного цикла только циклы while, do, for
yield (с Java 14) отдает значение из блока ветки switch-выражения меток нет только внутри switch-выражения
goto оператора нет оператора нет слово зарезервировано, в коде запрещено

Итоги

Мы рассмотрели различные операторы перехода в Java:
  1. return: завершение метода, возвращение значения из метода.
    • с возвращаемым значением: методы, которые возвращают значения;
    • без возвращаемого значения: void методы.
  2. break: прерывание циклов, switch-case блоки.
    • с метками: циклы различной вложенности;
    • без меток: ветки switch-case блока; прерывание цикла, в котором был вызван.
  3. continue.
    • с метками: циклы различной вложенности;
    • без меток: продолжение цикла, в котором был вызван.
  4. goto.
    • есть в списке ключевых слов, но не используется.
Вывод из всего этого простой: лучше отдавать предпочтение наиболее простым подходам, которые облегчают читаемость кода. Старайся не перегружать код многоуровневыми циклами, вложенными друг в друга с обилием меток, прерываний и продолжений.

Вопросы и ответы

Какие операторы перехода есть в Java?

Три: return, break и continue. С Java 14 к ним добавился yield, который отдает значение из блока ветки switch-выражения. Слово goto зарезервировано вместе с const, но оператора goto в языке нет: спецификация прямо говорит, что, в отличие от C и C++, метки в Java работают только с break и continue.

Чем break отличается от continue?

break завершает ближайший switch или цикл целиком: оставшиеся итерации не выполняются. continue прерывает только текущую итерацию и переходит к следующей проверке условия (в for перед этим еще выполняется секция приращения). break допустим в switch и циклах, continue только в циклах while, do и for.

Зачем нужен break с меткой?

Чтобы выйти сразу из нескольких вложенных циклов. Без метки break прерывает только внутренний цикл, а внешний продолжает итерации. Метка ставится перед внешним циклом (searchNegative:), а break searchNegative; завершает его целиком. По спецификации метка может помечать любой оператор, даже обычный блок в фигурных скобках, так что break label; выйдет и из блока.

Прервет ли break цикл, если он стоит внутри switch внутри цикла?

Нет. break без метки завершает ближайший из объемлющих switch, while, do или for, а ближайшим окажется switch. Цикл продолжит работу со следующей итерации. Чтобы выйти из цикла из ветки switch, пометьте цикл меткой и напишите break имяМетки;.

Почему goto есть в списке ключевых слов Java, но не работает?

По JLS 3.9 слова const и goto зарезервированы, хотя и не используются, чтобы компилятор мог выдать понятную ошибку, когда эти ключевые слова из C++ по ошибке попадут в Java-код. Инструкция goto есть только в байткоде JVM, куда компилятор переводит циклы и ветвления; в самом языке оператора goto никогда не было.

Можно ли обойтись без меток?

Почти всегда. Вложенный цикл выносится в отдельный метод, и тогда вместо break label; или continue label; достаточно return: в статье так переписан подсчет строк с отрицательными элементами через метод arrayHasNegativeElements(). Код без меток проще читать и тестировать, поэтому в промышленной разработке метки встречаются редко.

Читайте также