Для чего нужны 2 цикла: for и foreach?

184
13 апреля 2019, 23:00

Вопросы:

  1. Для чего нужен foreach, если можно перебирать массив к примеру через цикл for?

  2. Можно ли сделать задачу с массивами например на foreach и на цикле for обычном, чтобы получить по итогу один и тот же результат?

  3. Зачем нам foreach если есть for?

Нашел вот такой пример, но почему-то не получается реализовать его на foreach:

public class Test {
    public static void main(String[] args) {
        int numer[] = {4, 8, 16, 32, 64, 128};
        int denom[] = {2, 2, 4, 4, 2, 8};
        for (int i = 0; i < numer.length; i++) {
            System.out.println(numer[i] + " / " + denom[i] + " равно " + numer[i] / denom[i]);
        }
    }
}
Answer 1
  1. foreach укорачивает код для перебора коллекций (списки, массивы и т.п.), когда нам нужно только получить элементы из них, без работы с индексами.

    Примеры для foreach:

    int numer[] = {4, 8, 16, 32, 64, 128};
    for (int i : numer) {
        System.out.println(i);
    }
    List<String> items = Arrays.asList("1", "abc");
    for (String word : items) {
        System.out.println(word);
    }
    Map<String, String> nameByValue = new HashMap<>();
    nameByValue.put("name", "Vasya");
    nameByValue.put("email", "pupkin@email.mail");
    for (Map.Entry<String, String> entry : nameByValue.entrySet()) {
        System.out.println(entry.getKey() + " = " + entry.getValue());
    }

    Через стандартный цикл:

    int numer[] = {4, 8, 16, 32, 64, 128};
    for (int i = 0; i < numer.length; i++) {
        System.out.println(numer[i]);
    }
    List<String> items = Arrays.asList("1", "abc");
    for (int i = 0; i < items.size(); i++) {
        String word = items.get(i);
        System.out.println(word);
    }
    Map<String, String> nameByValue = new HashMap<>();
    nameByValue.put("name", "Vasya");
    nameByValue.put("email", "pupkin@email.mail");
    // Через for:
    // for (Iterator<Map.Entry<String, String>> it = nameByValue.entrySet().iterator(); it.hasNext();) {
    // 
    // Через while более предпочтительнее чем через for
    Iterator<Map.Entry<String, String>> it = nameByValue.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<String, String> entry = it.next();
        System.out.println(entry.getKey() + " = " + entry.getValue());
    }
  2. Можно. Но придется для foreach сгенерировать коллекцию с индексами.

  3. Чтобы код перебора коллекции был короче. Пример:

    int numer[] = {4, 8, 16, 32, 64, 128};
    for (int i = 0; i < numer.length; i++) {
        System.out.println(numer[i] + " * 2 -> " + numer[i] * 2);
    }
    System.out.println();
    for (int i : numer) {
        System.out.println(i + " * 2 -> " + i * 2);
    }

Код:

static List<Integer> getRange(int length) {
    List<Integer> items = new ArrayList<>();
    for (int i = 0; i < length; i++) {
        items.add(i);
    }
    return items;
}
public static void main(String[] args) throws Exception {
    int numer[] = {4, 8, 16, 32, 64, 128};
    int denom[] = {2, 2, 4,   4,  2, 8};
    for (int i = 0; i < numer.length; i++) {
        System.out.println(numer[i] + " / " + denom[i] + " равно " + numer[i] / denom[i]);
    }
    System.out.println();
    for (int i : getRange(numer.length)) {
        System.out.println(numer[i] + " / " + denom[i] + " равно " + numer[i] / denom[i]);
    }
}

Консоль:

4 / 2 равно 2
8 / 2 равно 4
16 / 4 равно 4
32 / 4 равно 8
64 / 2 равно 32
128 / 8 равно 16
4 / 2 равно 2
8 / 2 равно 4
16 / 4 равно 4
32 / 4 равно 8
64 / 2 равно 32
128 / 8 равно 16
Answer 2

foreach работает c итераторами.

List<String> someIterable = List.of("1", "2");
for (Iterator<String> i = someIterable.iterator(); i.hasNext();) {
    String item = i.next();
    System.out.println(item);
}

Этот цикл эквивалентен записи:

for(String str : someIterable){
    System.out.println(item);
}

У обычного массива итератор отсутствует.

Запись вида:

for (int numeri : numer) {
    System.out.println(numeri);
}

Является синтаксическим сахаром, так как преобразуется в следующий вид

int len = numer.length;
for (int i = 0; i < len; i++) {
    int numeri = numer[i];
    System.out.println(numeri);
}

Но тем не менее, foreach спроектирован для работы с текущим элементом одной структуры, а вам необходимо взять i-ый элемент у двух массивов numer и denom

Answer 3

Дополню ответ @gil9red информацией по другим сценариям использования for-each.

  1. Для чего нужен foreach, если можно перебирать массив к примеру через цикл for?

for-each, а точнее усовершенствованный цикл for (enhanced for loop), задумывался в первую очередь для облегчения работы с коллекциями через итератор. Поддержка массивов добавлена для единообразия кода.

Не все коллекции, которые поддерживают перебор через итератор, поддерживают обращение по индексу. Например, множество можно перебрать через for-each:

Set<String> letters = new HashSet<>(Arrays.asList("a", "b"));
for(String letter : letters) {
     //...
}

При этом цикл пройдет по всем элементам множества, но порядок элементов не гарантируется. Обращение к элементу множества по индексу не имеет смысла:

for(int i=0; i<letters.size(); i++) { 
     letters.get(i); //такого метода нет
}

Т.о. как минимум для коллекций for-each нельзя заменить циклом for по индексу. Для коллекций for-each соответствует циклу for с итератором:

Collection c = ... ;
for (Iterator i = c.iterator(); i.hasNext(); ) {  
    String s = (String) i.next();
}

Перебор коллекции это операция, которая выполняется очень часто, поэтому в язык и ввели синтаксический сахар, который позволяет сократить код и, соответственно, избежать трудноуловимых ошибок при постоянном использовании одного и того же шаблонного кода. Такое обоснование дается в поправке к спецификации Java (JSR-201) в которой был введен усовершенствованный цикл for:

Enhanced for loops allow convenient iteration over collections, without the need for an explicitly defined iterator. This reduces the need for boilerplate iteration code and the corresponding opportunities for errors.

Усовершенствованный цикл for предостваляет удобный способ перебора коллекций, без необходимости явного указания итератора. Это сокращает необходимость создания шаблонного кода для перебора и снижает вероятность возникновения связанных с ним ошибок.

Собственно, по такой же причине в Java имеется три разных оператора цикла (for, while, do-while) хотя, технически, можно было бы обойтись любым из них.

  1. Можно ли сделать задачу с массивами например на foreach и на цикле for обычном, чтобы получить по итогу один и тот же результат?

Можно вывернуться конечно, используя список индексов, как в ответе @gil9red, либо используя счетчик:

int counter = 0;
for (int n : numer) {
    System.out.println(n + " / " + denom[counter] + " равно " + n / denom[counter]);
    counter++;
}

либо создав целую иерархию классов:

DivisionBatch divisions = new DivisionBatch(numer, denom);
for(Division division : divisions) {
     System.out.println(division.getNumerator() + "/" + division.getDenominator() + " равно " + divison.getResult());
}

Но, по-моему, в данном примере преимущества for-each (сокращение кода для перебора) не проявляются и целесообразнее использовать for.

READ ALSO
JavaFX vs. Swing

JavaFX vs. Swing

Давненько не брал в руки шашку, как то решил переписать свою аппу под Android на толстого десктоп клиента и обнаружил, что Swing типа как бы умер

160
spring отказоустойчивость jdbc

spring отказоустойчивость jdbc

Есть две базы(PostgreSQL) дублирующие полностью друг другаВ нашем апи приложении раньше использовался один датасурс, т

178
Указатель стека при рекурсии

Указатель стека при рекурсии

Прошу объяснить, как работает указатель стека при рекурсивном вызове метода из самого себяПишу морской на Java

236
JavaFx прижать блоки к верху и к низу окна

JavaFx прижать блоки к верху и к низу окна

Мне нужно сделать, чтобы у меня три label были вверху окна (друг за другом) в полную ширину, один внизу окна, а в центре находилась формаМожет...

154