본문으로 바로가기

Java Enhanced for-loop vs Collecions.forEach()

category Java 2021. 9. 23. 22:21

 

https://wnwngus.tistory.com/57?category=891308 

 

Java for-loop vs Enhanced for-loop

알고리즘을 제외하고 다른 내용에 대해 포스팅을 하고 싶다고 생각해왔다. 사실 알고리즘을 올리는 것도, 별도의 프로젝트를 진행하고 정말 짧은 짬이 날 때 일주일 동안 풀었던 문제들에 대해

wnwngus.tistory.com

 

지난 번의 글에 이어서, 자바에서 forEach라고 했을 때 생각날법한 것들에 대해 추가적으로 더 정리해보고자 한다.

 

사실 자바를 접한지 얼마 되지 않았거나, 혹은 친숙하지 않은 경우 forEach만을 이야기한다고 하면 무엇을 말하는 것인지 헷갈릴 가능성이 농후하다.

 

지난 번 글에서 다른 Enhanced for-loop(향상된 for문)이라고 불리우는 것도 보통 forEach문이라고 지칭하며,

 

오늘 다룰

Collections.forEach(),

Collections.stream().forEach(),

Collections.parallelStream().forEach()도 모두 forEach이다.

 

오늘은 이것들을 정리하고, 각각의 차이점에 대해 알아볼까 한다.

 

사실 이 세 가지는 결과적으로는 같은 결과를 도출해내기때문에, 어마어마하게 큰 차이가 있는 것들이라고 보기는 어렵다. 그러나 분명 차이점이 존재하고, 그 차이점이 왜 발생하는 지를 알고 적절한 장소에 사용하는 것이 중요하다고 생각한다.

 

먼저 설명할 것은 Collections.forEach()이다.

 

이것은 내부 구조상으로 보았을 때는, 이전 글에서 설명한 Enhanced for-loop와 같다. iterator를 사용하여 반복 동작하는 방식이기때문에 사실상 같은 동작이라고 봐도 무방하다. 당연하지만 성능 상으로도 특별한 차이가 없다. Collections.forEach의 경우는 Array를 대상으로 사용할 수 없다는 점이 있지만, Array를 대상으로 Enhanced for-loop를 돌린 경우 내부적으로 for문으로 번역해주는 방식으로 동작하기 때문에, 사실 차이라고 보기도 어렵다.

다만 내부 동작에 대한 이해가 없으면, 에러의 타이밍에 대해 착각할 여지가 있을 수 있다고 생각되어 정확하게 반복의 도중 Collections에 대한 수정이 일어났을 때 구체적으로 '어떤 순간에' 에러가 발생되는지 보여주고자 한다.

 

나는 항상 에러가 어떠한 이유로, '어떠한 순간'에 발생하는 지 알고 있는 것은 굉장히 중요하다고 생각한다. 그것은 빠른 디버깅의 근원이기도하며, 또한 내가 그것을 정말 제대로 이해하고 있는 지에 대한 척도가 되기 때문이다.

 

ex) Enhanced for-loop 내부에서 Collections를 수정하고, 바로 break 하는 경우

 

        List<Integer> nums = new LinkedList<>(Arrays.asList(1, 2, 3, 4));

        for (int num : nums) {
            nums.add(5);
            break;
        }
        System.out.println(nums); // [1,2,3,4,5]

 

결과

 

 

이렇게 결과가 도출되는 이유는 무엇일까? 그것은 iterator의 동작에 대해 이해하고 있으면 충분히 납득할 수 있다. 추후 iterator와 관련한 포스팅을 하게된다면 추가적으로 더 설명하겠지만, 이전의 포스팅에서 설명했던 내부 구조를 보며 다시 고민해보자.

 


Iterator iter = list.iterator();

while(iter.hasNext()){ 
	Integer num = (Integer)iter.next(); // ERROR!
    //...
}

주석처리된 에러 발생 포인트를 보면, 데이터를 수정하고 next()를 호출한 시점에 에러가 발생한다는 사실을 알 수 있다. 이것은 Iterator 내부의 modCount와 expectedModCount에 차이가 있기 때문에 발생하는 에러이며, 이 에러의 발생 원인을 알고 있으면 위와 같이 break 문을 주어 회피할 수 있다.

 

break가 없다면 ConcurrentModificationException을 발생시킨다.

 

이외에도 다른 회피 방법이 있지만, 이번 글의 요점은 이것이 아니므로 추후에 다뤄보고자 한다. 요점은, Enhanced for-loop의 경우 컬렉션의 수정 이후 에러 발생의 타이밍이 다음을 조회하는 시점이라는 것이다.

 

그러나 Collections.forEach()는 어떨까? 당연하지만, 동일하다. 

        List<Integer> nums = new LinkedList<>(Arrays.asList(1, 2, 3, 4));

        nums.forEach((num ->
            nums.add(5))
        );
        System.out.println(nums);

이러한 코드가 작성되었다고 할 때, 디버깅으로 천천히 코드의 흐름을 따라가다보면

List의 Size가 5가 된 모습

List의 사이즈가 5가 된 이후, 

ConcurrentModificationException이 발생했다는 사실을 알 수 있다.

 

결론

 

- 요약하자면, Enhanced for-loop와 Collections.forEach()의 내부 동작은 동일하다.

- 당연하지만, 기타 성능 상의 차이점도 존재하지 않는다.

- 다만 활용에 따라서 Enhanced for-loop는 루프 내부에서 Collections에 대한 조작을 할 여지가 있다.

- Collecions.forEach()는 불가능하다.

 

 

글이 조금 길어지는 감이 있어, 나머지 두 forEach에 대해서는 다음 글에 정리해볼까한다.

 

'Java' 카테고리의 다른 글

Java Collections.forEach vs Collections.stream().forEach()  (0) 2021.09.28
Java for-loop vs Enhanced for-loop  (2) 2021.09.22