Управление потоком Java: для и для каждого цикла

Введение Условные операторы и циклы - очень важный инструмент в программировании. Есть немного вещей, которые мы могли бы сделать с кодом, который может выполняться только построчно. Вот что означает «управление потоком» - руководство выполнением нашей программы вместо того, чтобы позволить ей выполняться построчно, независимо от каких-либо внутренних или внешних факторов. Каждый язык программирования поддерживает ту или иную форму управления потоком, если не явно через ifs, fors или аналогичные операторы - тогда он неявно дает нам инструменты для

Вступление

Условные операторы и циклы - очень важный инструмент в программировании. Есть немного вещей, которые мы могли бы сделать с кодом, который может выполняться только построчно.

Вот что означает «управление потоком» - руководство выполнением нашей программы вместо того, чтобы позволить ей выполняться построчно, независимо от каких-либо внутренних или внешних факторов. Каждый язык программирования поддерживает некоторую форму управления потоком, если не явно через if s и for s или аналогичные операторы - тогда он неявно дает нам инструменты для создания таких конструкций, то есть языки программирования низкого уровня обычно достигают этого эффекта с большим go-to командам.

Циклы были концепцией, которая использовалась задолго до того, как компьютерное программирование стало вообще чем-то, но первой, кто использовал программный цикл, была Ада Лавлейс, широко известная под своей девичьей фамилией - Байрон, при вычислении чисел Бернулли еще в 19 веке.

В Java есть несколько способов управления потоком кода:

Цикл 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 , и он выполняется следующим образом:

  1. Установите для локальной переменной i значение 0
  2. Проверьте, меньше i arr.length , если это продолжается в пределах блока
    2.1. Распечатать arr[i]
    2.2. Увеличить i на 1, перейти к шагу 2.
  3. Если 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 или увеличение и проверка нескольких переменных.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus