Вступление
Условные операторы и циклы - очень важный инструмент в программировании. Есть немного вещей, которые мы могли бы сделать с кодом, который может выполняться только построчно.
Вот что означает «управление потоком» - руководство выполнением нашей
программы вместо того, чтобы позволить ей выполняться построчно,
независимо от каких-либо внутренних или внешних факторов. Каждый язык
программирования поддерживает некоторую форму управления потоком, если
не явно через if
s и for
s или аналогичные операторы - тогда он
неявно дает нам инструменты для создания таких конструкций, то есть
языки программирования низкого уровня обычно достигают этого эффекта с
большим go-to
командам.
Циклы были концепцией, которая использовалась задолго до того, как компьютерное программирование стало вообще чем-то, но первой, кто использовал программный цикл, была Ада Лавлейс, широко известная под своей девичьей фамилией - Байрон, при вычислении чисел Бернулли еще в 19 веке.
В Java есть несколько способов управления потоком кода:
- операторы if и if-else
- операторы переключения
- утверждения
while
иdo-while
for
иenhanced for
операторов- операторы
break
иcontinue
Цикл for
for
обычно используются, когда количество итераций каким-либо образом
"фиксировано". Либо мы точно знаем, сколько раз будет выполнен цикл,
либо у нас есть лучшее представление, чем «пока n не станет m».
Циклы For часто используются с массивами:
for (initialization; terminationCondition; update) {
// Code here...
}
И простая реализация:
int[] arr = {1,2,3,4,5,6,7,8,9};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
Это очень простой for
, и он выполняется следующим образом:
- Установите для локальной переменной
i
значение 0 - Проверьте, меньше
i
arr.length
, если это продолжается в пределах блока
2.1. Распечататьarr[i]
2.2. Увеличитьi
на 1, перейти к шагу 2. - Если
i
не меньшеarr.length
, продолжайте после блока
Как только шаг 2 обнаруживает, что i
больше или равно arr.length
,
цикл останавливает свое выполнение, и Java продолжает выполнение со
строки после блока цикла.
Примечание: здесь размещение оператора приращения ( ++
) не имеет
значения. Если бы вместо этого наш шаг обновления был ++i
, ничего бы
не изменилось, поскольку шаг обновления всегда выполняется после кода в
блоке цикла for
Этот код, конечно, распечатает:
1
2
3
4
5
6
7
8
9
for
используются три разных блока, которые позволяют нам это делать:
блок инициализации , условие завершения и шаг обновления .
Блок инициализации
Блок инициализации в for
используется для инициализации переменной. В
нашем примере мы хотели, чтобы переменная i
начиналась с 0, поскольку
0 - это первый индекс в массиве.
Этот блок выполняется только один раз в начале цикла for
. Здесь мы
также можем определить несколько переменных одного типа:
// Single variable
for (int i = 0; i < 10; i++) {
// Code
}
// Multiple variables
for (int i = 10, j = 25; i < arr.length; i++) {
// Code
}
// Multiple variables
for (int i = 10, double j = 25.5; i < arr.length; i++) {
// WON'T compile because we used two different types - int and double
}
Переменные, инициализированные внутри for
могут использоваться только
внутри блока for
Доступ к ним за пределами их области действия
приведет к исключению, как и ожидалось:
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println(i);
Переменная i
была указана вне области видимости:
MyClass.java:6: error: cannot find symbol
System.out.println(i);
^
symbol: variable i
location: class MyClass
1 error
Примечание . Код в блоке инициализации не является обязательным и его не нужно включать. Хотя блок должен быть. Следовательно, мы можем написать что-то вроде этого:
int i = 0;
for (; i < 10; i++) {
System.out.println(i);
}
System.out.println("\ni variable is " + i);
И это приведет к такому же результату, как если бы там был блок
инициализации, за исключением того, что i
больше не выходит за пределы
области действия после выполнения цикла for
:
0
1
2
3
4
5
6
7
8
9
i variable is 10
Блок инициализации технически присутствует, поскольку мы включили ;
конец, но внутри него нет кода.
Условия прекращения действия
Условие завершения указывает for
выполнять код, пока он true
. Если
условие завершения оценивается как false
, шаг обновления и остальная
часть цикла for
пропускаются. Условие прекращения может быть только
одно:
for (int i = 0; i < 10; i++) {
System.out.println("i = " + i + " | i < 10 is true");
}
// Compiles as there's only one termination condition,
// although there's two operators - two relational and one logical
for (int i = 6; i < 10 & i > 5; i++) {
System.out.println("i = " + i + " | i < 10 is true");
}
// WON'T compile, multiple separate termination conditions
for (int i = 0; i < 10, i > 5; i++) {
System.out.println("i = " + i + " | i < 10 is true");
}
Выход первого цикла:
i = 0 | i < 10 is true
i = 1 | i < 10 is true
i = 2 | i < 10 is true
i = 3 | i < 10 is true
i = 4 | i < 10 is true
i = 5 | i < 10 is true
i = 6 | i < 10 is true
i = 7 | i < 10 is true
i = 8 | i < 10 is true
i = 9 | i < 10 is true
При достижении 10
условие i < 10
перестает true
и код перестает
зацикливаться.
И вывод второго цикла:
i = 6 | i < 10 is true
i = 7 | i < 10 is true
i = 8 | i < 10 is true
i = 9 | i < 10 is true
Примечание. Условие завершения также является необязательным.
Допустимо определять for
без условия завершения. Однако исключение
условия завершения приведет к бесконечному циклу кода или до тех пор,
пока не произойдет StackOverflowError
for (int i = 0; ; i++) {
System.out.println("Looping forever!");
}
Несмотря на то, что в условии завершения нет кода, вы должны поставить точку с запятой, чтобы отметить, что вы решили оставить его пустым, иначе код не будет компилироваться.
MyClass.java:3: error: ';' expected
for (int i = 0; i++) {
^
1 error
Шаг обновления
Шаг обновления чаще всего уменьшает / увеличивает какую-то управляющую
переменную (в нашем случае - i
) после каждой итерации цикла. По сути,
он обеспечивает выполнение нашего условия завершения в какой-то момент -
в нашем случае увеличивает i
до тех пор, пока не достигнет 10.
Шаг обновления будет выполняться только в том случае, если условие
завершения оценивается как true
, и после того, как код в for
был
выполнен. Вы можете выполнить несколько шагов обновления, если хотите, и
даже можете вызывать методы:
for (int i = 0; i < 10; i++) {
// Code
}
int j = 10;
for (int i = 0; i < 10; i++, j--) {
System.out.println(i + " | " + j);
}
// Multiple variables
for (int i = 10; i < arr.length; i++, doSomething()) {
System.out.println("Hello from the for loop");
}
public static void doSomething() {
System.out.println("Hello from another method");
}
Результатом этого цикла будет:
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
Hello from the for loop
Hello from another method!
Это также подтверждает, что этап обновления выполняется после кода внутри блока.
Примечание: этап обновления также необязателен, как и блок
инициализации и условие завершения. Его можно заменить кодом внутри
цикла for
, и мы также можем увеличивать / уменьшать управляющую
переменную перед кодом.
Пусто для цикла?
Поскольку все три блока цикла for
технически необязательны, мы могли
бы без проблем написать это for
цикла:
int i = 0;
// This will compile, all blocks are "present" but no code is actually there
for (;;) {
if (i < 10)
System.out.println(i);
i++;
}
Если вы присмотритесь, это действительно похоже на цикл while :
int i = 0;
while (i < arr.length) {
System.out.println(i);
i++;
}
Все , что может быть сделано с в while
цикла может быть сделано с
for
цикла, и наоборот, и выбор между ними решается читабельности и
удобства.
for
и три части дают четкое представление о том, каковы условия цикла,
где он начинается, как изменяется состояние и когда он прекращает
повторение. Это делает их очень краткими и легко управляемыми.
Учитывая это, настоятельно рекомендуется избегать изменения управляющей
переменной ( i
) вне скобок после for
, за исключением случаев
крайней необходимости.
Вложенные для циклов
Вложенные for
также очень распространены, особенно при итерации через
многомерные массивы (массивы, которые имеют другие массивы в качестве
элементов):
int[][] multiArr = {{1,2,3},{4},{5,6}};
for (int i = 0; i < multiArr.length; i++) {
for (int j = 0; j < multiArr[i].length; j++) {
System.out.print(multiArr[i][j] + " ");
}
System.out.println();
}
Выполнение этого кода даст:
1 2 3
4
5 6
Улучшено для цикла
Расширенный for
или for-each
- это особый тип цикла for
который
можно использовать для любого объекта, реализующего Iterable
и
массивов.
Он предназначен для прохождения элементов по порядку, один за другим, от
начала до конца. Это отличается от традиционного for
тем, что мы могли
бы сделать шаг, какой захотим - например, можно было бы распечатать все
остальные элементы:
int[] arr = {1,2,3,4,5,6,7,8,9};
for (int i = 0; i < arr.length; i+=2) {
System.out.println(arr[i]);
}
Обратите внимание, что мы i
на 2. Это распечатало бы следующее:
1
3
5
7
9
Вместо этого с помощью for-each
мы перебираем все элементы, используя
следующий синтаксис:
for (ElementType localVar : somethingIterable) {
// Code
}
Цикл сохраняет один элемент за другим в localVar
, пока не останется
больше элементов. Он был создан, чтобы избежать традиционных for
,
последовательно проходящих через массивы / коллекции. Нам больше не
нужно указывать счетчик, устанавливать начальную и конечную позиции,
вручную индексировать массив, и нам больше не нужно беспокоиться о
границах - все это может быть очень утомительно для записи.
Все это было автоматизировано через for-each
.
List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));
for (String s : list) {
System.out.println(s);
}
// The traditional for syntax...
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// ...or...
for (Iterator<String> i = list.iterator(); i.hasNext();) {
System.out.println(i.next());
}
Первый цикл самый чистый и требует меньшего обслуживания с нашей
стороны. Этот фрагмент кода может быть эффективно читаться как: «для
каждой строки s
в списке строк list
, сделать что - то s
.
Вложенные для каждого цикла
for-each
также поддерживает многомерные массивы, один из наших
предыдущих for
распечатывающих элементы двумерного массива - вот как
это будет выглядеть при использовании for-each
:
int[][] multiArr = {{1,2,3},{4},{5,6}};
// Keep in mind that x is an array, since multiArr
// is an array of arrays
for (int[] x : multiArr) {
for (int y : x) {
System.out.print(y + " ");
}
System.out.println();
}
Точно так же мы можем использовать его для итерации вложенных коллекций:
ArrayList<String> stronglyTyped = new ArrayList<>();
stronglyTyped.add("Java");
stronglyTyped.add("Go");
stronglyTyped.add("Harbour");
stronglyTyped.add("Haskell");
ArrayList<String> weaklyTyped = new ArrayList<>();
weaklyTyped.add("C++");
weaklyTyped.add("C");
weaklyTyped.add("JavaScript");
ArrayList<ArrayList<String>> programmingLanguages = new ArrayList<ArrayList<String>>();
programmingLanguages.add(stronglyTyped);
programmingLanguages.add(weaklyTyped);
В programmingLanguages
ArrayList содержит два других ArrayLists -
stronglyTyped
и weaklyTyped
.
Чтобы пройти по ним, мы просто напишем:
for (ArrayList<String> languages : programmingLanguages) {
for (String language : languages) {
System.out.println(language);
}
}
Выход:
Java
Go
Harbour
Haskell
C++
C
JavaScript
Изменение значений во время для каждого
Важно отметить, что вы можете изменять значения повторяемых элементов.
Например, в предыдущем примере, если изменить
System.out.println(language)
на
System.out.println(language.toUppercase())
, нас встретят:
JAVA
GO
HARBOUR
HASKELL
C++
C
JAVASCRIPT
Это потому, что мы имеем дело с объектами. Выполняя итерацию по ним, мы
присваиваем их ссылочные переменные language
строке. Любое изменение
language
справочной переменной будет отражено и в исходной. В случае
строк это может не повлиять на объекты из-за пула строк, но суть вы
поняли.
Однако этого не происходит с примитивными переменными, поскольку в случае с ними значение просто копируется в переменную, к которой мы обращаемся. Поэтому его изменение не повлияет на исходные переменные.
Бонус: метод forEach
Хотя это не является предметом внимания этой статьи, мы должны
упомянуть, что появился новый способ .forEach()
, который может быть
связан с лямбда-выражениями для
однострочных циклов.
В некоторых случаях это можно использовать вместо цикла for-each
, что
еще больше упрощает итерацию:
list.forEach(x -> System.out.println(x));
Заключение
Управление потоком в коде важно практически для каждого приложения. Заявления, которые изменяют поток кода, являются фундаментальными строительными блоками, и каждый начинающий разработчик должен полностью контролировать / осознавать, как они работают.
for
и for-each
хороши для выполнения блока кода известное количество
раз, часто с массивом или другим типом итерации. Также возможны более
сложные циклы, такие как увеличение индекса на 2 или увеличение и
проверка нескольких переменных.