๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๊ฐœ์ธ ๊ณต๋ถ€

Baeldung ํ•ด์„

by syLim___ 2024. 11. 21.
728x90

The Difference Between Collection.stream().forEach() and Collection.forEach()

 

1. Overview

์ž๋ฐ”์—์„œ ์ปฌ๋ ‰์…˜์„ ๋ฐ˜๋ณตํ•˜๋Š” ์˜ต์…˜์—๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” 2๊ฐ€์ง€ ๋น„์Šทํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ดํŽด๋ณธ๋‹ค.

๋Œ€๋ถ€๋ถ„ ๋‘˜๋‹ค ๋™์ผํ•œ ๊ฒฝ๊ณผ๋ฅผ ๋ณด์ด์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ๋ฏธ๋ฌ˜ํ•œ ์ฐจ์ด์ ์„ ๋ณผ ์˜ˆ์ •์ด๋‹ค.

2. Simple List

a. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ํ–ฅ์ƒ for๋ฌธ์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  for(String s : list) {
    // do something with s
  }
 

b. ๋žŒ๋‹ค์‹์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค(Funtional-style)

   Consumer<String> consumer = s -> { System.out::println };
   list.forEach(consumer);
 

c. stream์˜ forEach()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

   list.stream().forEach(consumer);
 

 

3. Execution Order (์‹คํ–‰ ์ˆœ์„œ)

์•„๋งˆ ์—ฌ๊ธฐ์„œ ์ฐจ์ด๊ฐ€ ๋‚˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

  • Collection.foreach()์˜ ๊ฒฝ์šฐ ์š”์†Œ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ์ˆœ์„œ๋ฅผ ์ง์ ‘ ์ •์˜ํ•œ๋‹ค.
  • ๋ฐ˜๋ฉด Collection.stream().foreach()์˜ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ ์ˆœ์„œ๋ฅผ ์ •์˜๋˜์ง€ ์•Š๋Š”๋‹ค. ์œ„์˜ ๊ฒฝ์šฐ ๋‘ ๊ฐ€์ง€๋Š” ํฌ๊ฒŒ ์ฐจ์ด๊ฐ€ ๋‚˜์ง€ ์•Š๋Š”๋‹ค.

3.1 Parallel Streams (๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ)

๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ(multi thread)์—์„œ ์ŠคํŠธ๋ฆผ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ๋Š” ์‹คํ–‰ ์ˆœ์„œ๋Š” ์ •์˜๋˜์ง€ ์•Š๋Š”๋‹ค. java๋Š” Collectors.toList()์™€ ๊ฐ™์€ ํ„ฐ๋ฏธ๋„ ์ž‘์—…์ด ํ˜ธ์ถœ๋˜๊ธฐ ์ „์— ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๋„๋ก ์š”๊ตฌํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” ํ„ฐ๋ฏธ๋„ ์ž‘์—…์€ ์ŠคํŠธ๋ฆผ์˜ ์ฒ˜๋ฆฌ๋ฅผ ๋๋‚ด๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ž‘์—…์„ ๋งํ•œ๋‹ค. ์ฆ‰ ์ŠคํŠธ๋ฆผ์„ ์†Œ๋น„ํ•˜๊ณ , ์ŠคํŠธ๋ฆผ์— ๋Œ€ํ•œ ๋” ์ด์ƒ์˜ ์ž‘์—…์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
ex) list.forEach(System.out::println), list.stream().collect(Collectors.toList()), list.stream().reduce(~) ๋“ฑ๋“ฑ

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด์ž

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);
 


์ฝ”๋“œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰ํ•˜๋ฉด list.forEach()๊ฐ€ ์‚ฝ์ž… ์ˆœ์„œ๋Œ€๋กœ ํ•ญ๋ชฉ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋ฐ˜๋ฉด list.parallelStream().forEach()๋Š” ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค
์‹คํ–‰ ๊ฒฐ๊ณผ ![img_1.png](img_1.png)

3.2 Custom Iterators

๊ฐ„๋‹จํ•˜๊ฒŒ ์—ญ์ˆœ์œผ๋กœ ๋ฐ˜๋ณตํ•˜๋Š” Iterator๋ฅผ ์ •์˜ํ•ด์„œ ์‹คํ–‰์‹œ์ผœ ๋ณด์ž.

class ReverseList extends ArrayList<String> {

    @Override
    public Iterator<String> iterator() {

        int startIndex = this.size() - 1;
        List<String> list = this;

        Iterator<String> it = new Iterator<String>() {

            private int currentIndex = startIndex;

            @Override
            public boolean hasNext() {
                return currentIndex >= 0;
            }

            @Override
            public String next() {
                String next = list.get(currentIndex);
                currentIndex--;
                return next;
             }

             @Override
             public void remove() {
                 throw new UnsupportedOperationException();
             }
         };
         return it;
    }
}
 



๊ทผ๋ฐ Bealdung์—์„œ Collection.forEach()์—์„œ ๋‚ด๋ถ€์ ์œผ๋กœ custom iterator๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ์ฒ˜๋Ÿผ ๋‚˜์™€์žˆ๊ณ 
์˜ˆ๋ฌธ์—๋„ ์•„๋ž˜์ฒ˜๋Ÿผ ๋‚˜์™€์žˆ๋Š”๋ฐ ์ง์ ‘ ๋Œ๋ ค๋ณธ ๊ฒฐ๊ณผ ์•„๋‹ˆ์˜€๋‹ค.

List<String> myList = new ReverseList();
myList.addAll(list);

myList.forEach(System.out::print); 
System.out.print(" "); 
myList.stream().forEach(System.out::print);
 



์˜ˆ์ƒ ๊ฒฐ๊ณผ ![img_2.png](img_2.png)

์‹ค์ œ ๊ฒฐ๊ณผ ![img_3.png](img_3.png)

์ปดํŒŒ์ผ๋œ ์ฝ”๋“œ๋„ ๋ณด๋ฉด iterator๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ณ  Collection.forEach()์™€ ์ •์˜ํ•œ Consumer ๋กœ์ง๋Œ€๋กœ ์ถœ๋ ฅ ํ•œ๋‹ค. ![img_5.png](img_5.png)

์‹ค์ œ๋กœ ๋ฐ˜๋Œ€๋กœ ์ถœ๋ ฅํ•˜๊ณ  ์‹ถ์œผ๋ฉด, ์•„๋ž˜์ฒ˜๋Ÿผ
- custom iterator๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ - enhanced for-loop๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. (๋‚ด๋ถ€์ ์œผ๋กœ ๊ตฌํ˜„ํ•œ iterator๋ฅผ ์‚ฌ์šฉ)

List<String> myList = new ReverseList();
        myList.addAll(list);

        Iterator iterator = myList.iterator();
        System.out.println("custom iterator ์ง์ ‘ ์‚ฌ์šฉ");
        while(iterator.hasNext()) {
            System.out.print(iterator.next());
        }
        System.out.println();

        // ๊ตฌํ˜„ํ•œ iterator๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Œ
        System.out.println("enhanced for-loop");
        for(String s : myList) {
            System.out.print(s);
        }
        System.out.println();

        // ์ฐพ์•˜๋‹ค ๋ฒ”์ธ, ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ forEach๋ฅผ ์ปดํŒŒ์ผ ํ• ๋•Œ iterable๋กœ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ
        // ๊ทธ๋ƒฅ list์˜ foreach๋ฌธ์„ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ์Œ
        // foreachํ•จ์ˆ˜๋ฅผ ๋ณด๋ฉด ์•ˆ์—์„œ ํ–ฅ์ƒfor๋ฌธ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ธด Consumer ํด๋ž˜์Šค(Functional ํด๋ž˜์Šค) ๋Œ€๋กœ ๋™์ž‘
        System.out.println("๊ทธ๋ƒฅ forEach() ์‚ฌ์šฉ");
        myList.forEach(x -> System.out.print(x));
        System.out.println();

        //stream์˜ foreach ํ˜ธ์ถœ
        System.out.println("์ŠคํŠธ๋ฆผ forEach() ์‚ฌ์šฉ");
        myList.stream().forEach(System.out::print);
 


์‹คํ–‰ ๊ฒฐ๊ณผ ![img_4.png](img_4.png)

์ „์ฒด ์ปดํŒŒ์ผ ์ฝ”๋“œ

public class JavaCollectionStreamForeach {
    public JavaCollectionStreamForeach() {
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList("A", "B", "C", "D");
        List<String> myList = new ReverseList();
        myList.addAll(list);
        Iterator iterator = myList.iterator();
        System.out.println("custom iterator ์ง์ ‘ ์‚ฌ์šฉ");

        while(iterator.hasNext()) {
            System.out.print(iterator.next());
        }

        System.out.println();
        System.out.println("enhanced for-loop");
        Iterator var4 = myList.iterator();

        while(var4.hasNext()) {
            String s = (String)var4.next();
            System.out.print(s);
        }

        System.out.println();
        System.out.println("๊ทธ๋ƒฅ forEach() ์‚ฌ์šฉ");
        myList.forEach((x) -> {
            System.out.print(x);
        });
        System.out.println();
        System.out.println("์ŠคํŠธ๋ฆผ forEach() ์‚ฌ์šฉ");
        Stream var10000 = myList.stream();
        PrintStream var10001 = System.out;
        Objects.requireNonNull(var10001);
        var10000.forEach(var10001::print);
    }
}
 


๊ทธ๋Ÿฌ๋ฉด์„œ `The reason for the different results is that forEach() used directly on the list uses the custom iterator` ๋ผ๊ณ  ๋˜์–ด์žˆ๋Š”๋ฐ iterator๊ฐ€ ์•„๋‹ˆ๋ผ ์ •์˜ํ•œ Functional-class ๋Œ€๋กœ ์ถœ๋ ฅ๋œ๋‹ค.

4. Modification of the Collection (์ปฌ๋ ‰์…˜ ์ˆ˜์ •)

๋Œ€๋ถ€๋ถ„์˜ ์ปฌ๋ ‰์…˜์€ ๋ฐ˜๋ณต(iterating)ํ•˜๋Š” ๋™์•ˆ์—๋Š” ๊ตฌ์กฐ์ ์œผ๋กœ ์ˆ˜์ •ํ•˜๋ฉด ์•ˆ๋œ๋‹ค.
๋งŒ์•ฝ, ๋ฐ˜๋ณตํ•˜๋Š” ๋™์•ˆ ์š”์†Œ๊ฐ€ ์‚ญ์ œ๋˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€๋˜๋ฉด ConcurrentModification exception ์ด ๋ฐœ์ƒํ•œ๋‹ค.
๊ฒŒ๋‹ค๊ฐ€ ์ปฌ๋ ‰์…˜์€ ๋น ๋ฅด๊ฒŒ ์‹คํŒจ(fast fail)๋˜๋„๋ก ์„ค๊ณ„ ๋˜์–ด์žˆ๋‹ค. ์ฆ‰, ์ˆ˜์ •์ด ๋ฐœ์ƒํ•˜๋ฉด ์ฆ‰์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์ค‘์— ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๋ฉด ConcurrentModification ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‚˜์ค‘์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

 

4.1 Removing an Element

"D"๋ฅผ ์‚ญ์ œํ•˜๋Š” Consumer ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ 

Consumer<String> removeElement = s -> {
    System.out.println(s + " " + list.size());
    if (s != null && s.equals("A")) {
        list.remove("D");
    }
};
 
  • remove() ๋ฉ”์„œ๋“œ๋Š” AbstractCollection์˜ remove() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ
  • ๋‚ด๋ถ€์—์„œ iterator๋ฅผ ํ†ตํ•ด iterator์˜ remove๋ฅผ ์ œ ํ˜ธ์ถœํ•œ๋‹ค. 



iterator ์ธํ„ฐํŽ˜์ด์Šค์— ์ •์˜ ๋˜์–ด์žˆ๋Š” remove()๋ฉ”์„œ๋“œ๋ฅผ ๋ณด๋ฉด ํ•ญ์ƒ `UnsupportedOperationException` ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๊ฒŒ ๋˜์–ด์žˆ๋‹ค. ![img_7.png](img_7.png)

์‹คํ–‰๊ฒฐ๊ณผ ![img_8.png](img_8.png)

#### forEach() ๋ฉ”์„œ๋“œ๋Š” ์‹คํŒจ๊ฐ€ ๋นจ๋ผ์„œ, ๋ฐ˜๋ณต์„ ์ค‘์ง€ํ•˜๊ณ  ๋‹ค์Œ ์š”์†Œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ „์— ์˜ˆ์™ธ๋ฅผ ํ™•์ธํ•œ๋‹ค.

์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” stream().forEach()๋ฅผ ์‚ดํŽด๋ณด์ž

list.stream().forEach(removeElement);
 

๊ต์žฌ์—์„œ๋Š” ๋Œ๋ ค๋ณด๋ฉด ์•„๋ž˜ ๊ฒฐ๊ณผ์ฒ˜๋Ÿผ ๋‚˜์˜จ๋‹ค๊ณ  ํ•œ๋‹ค 



๊ทผ๋ฐ ์‹ค์ œ๋กœ ๋Œ๋ ค๋ณด๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ๋‚˜์˜จ๋‹ค... 



#### stream()์˜ forEach()๋Š” Collection.forEach()์™€ ๋‹ค๋ฅด๊ฒŒ ๋ชจ๋“  ์š”์†Œ๋ฅผ ํ™•์ธํ•˜๊ณ  ์˜ˆ์™ธ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค๋Š”๊ฑธ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์€ ๊ฒƒ ๊ฐ™๋‹ค.

4.2. Changing Elements

์ค‘๊ฐ„์— ๊ฐ’์„ ๋ฐ”๊พธ๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด๋ณด๋ฉด

list.forEach(e -> {
            list.set(1, "E");
        });

        list.forEach(System.out::print);
 

์‹คํ–‰๊ฒฐ๊ณผ 



์œ„ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค?!?!
๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ Java์—์„œ๋Š” ์ŠคํŠธ๋ฆผ์— ๋Œ€ํ•œ ์ž‘์—…์ด ๊ฐ„์„ญํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด๋Š” ์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰ ์ค‘์— ์š”์†Œ๋ฅผ ์ˆ˜์ •ํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค

๊ทธ ์ด์œ ๋Š” ์ŠคํŠธ๋ฆผ์ด ๋ณ‘๋ ฌ ์‹คํ–‰์„ ์šฉ์ดํ•˜๊ฒŒ ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ŠคํŠธ๋ฆผ ์š”์†Œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋™์ž‘์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. Conclusion

์ปฌ๋ ‰์…˜์„ ๋ฐ˜๋ณตํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•๋งŒ ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด๋ผ๋Š” ์ ์— ์œ ์˜ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ‘œ์‹œ๋œ ๋™์ž‘์— ๋”ฐ๋ผ ์ •ํ™•์„ฑ์ด ๊ฒฐ์ •๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

์ŠคํŠธ๋ฆผ์€ ํ•„์š”ํ•˜์ง€ ์•Š์ง€๋งŒ ์ปฌ๋ ‰์…˜์— ๋Œ€ํ•ด์„œ๋งŒ ๋ฐ˜๋ณตํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์ฒซ ๋ฒˆ์งธ ์„ ํƒ์€ ์ปฌ๋ ‰์…˜์—์„œ ์ง์ ‘ forEach()๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

728x90