JavaRush /Java блог /Random /Операторы перехода в Java
Автор
Владимир Портянко
Java-разработчик в Playtika

Операторы перехода в Java

Статья из группы Random
Привет! Сегодня поговорим об операторах перехода в языке Java:
  • return
  • break
  • continue
  • goto
Для начала определимся с тем, что это вообще такое. Как известно, в обычной ситуации программа выполняется линейно — сверху вниз, команда за командой. Линейный ход программы могут изменить так называемые управляющие конструкции: например, ветвления (if) и циклы (for, while и тд). Помимо управляющих конструкций, линейное выполнение программы могут изменить операторы перехода. Они отвечают за перенаправление выполнения программы в определенное место, которое зависит от контекста и конкретного оператора.Операторы перехода в Java - 1Давай рассмотрим каждый из четырех операторов повнимательнее.

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[1];
        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; // Синтаксис оператора без метки
В блоках 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);
    }
В циклах оператор 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 совместно с указанием метки прерывает нужный цикл. Рассмотрим пример, в котором нам необходимо понять, есть ли отрицательный элемент, но только не в массиве, а в матрице:

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;
               }
           }
       }
}

сontinue

У оператора continue также есть две формы — с меткой и без:

continue; // форма оператора без метки
continue labelName; // форма оператора с меткой
В отличии от оператора break, который прерывает все оставшиеся итерации цикла, оператор continue прерывает текущую итерацию и приводит к запуску следующей.Операторы перехода в Java - 2Такое может быть полезно, если нужно провести некоторые операции над элементами, которые удовлетворяют определенным условиям. Скажем, у нас есть строка, и мы хотим посчитать количество слов, начинающихся с буквы “м”:

    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 совместно с меткой также используется при переборе элементов. Представим себе матрицу, в которой нам нужно посчитать количество строк с отрицательными элементами:

    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 можно по-разному использовать для достижения одной и той же функциональности. Так, можно переписать последний пример и вместо 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 (не используется). Дело в том, что Джеймс Гослинг, создатель ЯП Java изначально заложил в JVM поддержку оператора goto. Однако впоследствии эту фичу выпилили. Одна из причин заключается в том, что блоки кода содержащие оператор goto, читались не так хорошо, как блоки кода, выполняющие те же функции, но без goto, а с альтернативными подходами (break, continue, вынесение блока кода в методы). Были, собственно и другие, как например:
  • сложность чтения и понимания кода, в котором есть операторы goto;
  • усложнение оптимизации кода компилятору (а иногда и невозможность);
  • повышение вероятности создания трудно уловимых ошибок в коде.
Для многих не секрет, что в некоторых языках программирования оператор goto вполне успешно функционирует. Однако программисты избегают его использование. Почитать о причинах этого можно в одной статье на хабре. Но зачем тогда оставлять goto в списке зарезервированных слов? Все просто: на будущее. Если, к примеру, по всему миру в коде Java разработчиков переменные, методы или классы будут называться goto, если этот оператор вернут в одной из будущих версий Java, весь старый код сломается. Чтобы избежать такого сценария, goto остался в списке ключевых слов Java, но не несет в себе никакой функциональности. Возможно когда-нибудь goto вернется в наши ряды, но вероятность этого невысока.

Итоги

Мы рассмотрели различные операторы перехода в Java:
  1. return — завершение метода, возвращение значения из метода.
    • с возвращаемым значением: методы, которые возвращают значения;
    • без возвращаемого значения: void методы.
  2. break — прерывание циклов, switch-case блоки.
    • с метками: циклы различной вложенности;
    • без меток: ветки switch-case блока; прерывание цикла, в котором был вызван.
  3. continue.
    • с метками: циклы различной вложенности;
    • без меток: продолжение цикла, в котором был вызван.
  4. goto.
    • есть в списке ключевых слов, но не используется.
Вывод из всего этого простой: лучше отдавать предпочтение наиболее простым подходам, которые облегчают читаемость кода. Старайся не перегружать код многоуровневыми циклами, вложенными друг в друга с обилием меток, прерываний и продолжений.
Комментарии (12)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Дмитрий Уровень 2
14 августа 2020

int max = array[1];
    for (int i = 1; i < array.length; i++) {
В методе поиска максимального элемента в массиве, решили 0 элемент не рассматривать? Надо поправить на

int max = array[0];
Ivan Vdovin Уровень 9
13 августа 2020
Такс, я ввёл программу и ничего, ссылку браузер не открыл, просто ничего не произошло
bighugеmistеr Уровень 15
12 августа 2020
Меня очень смутила статья. Неужели важнее писать красивый и простой код на Java, а не ориентироваться на производительность? Я вообще много раз читал, что использование всех этих операторов (кроме return) — быдлокод, и нормальная программа с продуманным алгоритмом не нуждается в них
bighugеmistеr Уровень 15
12 августа 2020
Есть break, continue... А нет в Java какого-нибудь exit? Помню, в Pascal он был