Операторы перехода в 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(). Код без меток проще читать и тестировать, поэтому в промышленной разработке метки встречаются редко.

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