Операторы перехода в 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 14switchсо стрелкой->не требуетbreak, аyieldотдает значение из switch-выражения.
returnbreakcontinuegoto
if) и циклы (for, while и т. д.).
Помимо управляющих конструкций, линейное выполнение программы могут изменить операторы перехода. Они отвечают за перенаправление выполнения программы в определенное место, которое зависит от контекста и конкретного оператора.return
Именно с этим оператором чаще всего в первую очередь знакомятся новички. Операторreturn завершает работу метода, в котором его вызвали, и выполнение программы возвращается туда, откуда данный метод вызван.
У return есть две формы:- Немедленно заканчивает выполнение метода.
- Немедленно заканчивает выполнение метода и возвращает какое-то значение в качестве результата работы метода.
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:
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 используется в двух случаях:
- Для завершения какой-либо ветки выполнения в блоке switch-case.
- Для прерывания выполнения цикла.
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 прерывает текущую итерацию и приводит к запуску следующей.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:return: завершение метода, возвращение значения из метода.- с возвращаемым значением: методы, которые возвращают значения;
- без возвращаемого значения:
voidметоды.
break: прерывание циклов, switch-case блоки.- с метками: циклы различной вложенности;
- без меток: ветки switch-case блока; прерывание цикла, в котором был вызван.
continue.- с метками: циклы различной вложенности;
- без меток: продолжение цикла, в котором был вызван.
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(). Код без меток проще читать и тестировать, поэтому в промышленной разработке метки встречаются редко.