Java

[JAVA] 스트림_내부반복자

개바리여리 2025. 10. 13. 21:19

  스트림(내부 반복자)

 

 

우리가 보통 자주 쓰는 for문과 Iterator는 외부 반복자에 해당한다. 

외부반복자는 컬렉션의 요소를 순차적으로 접근하기 위해 컬렉션 외부에서 제공되는 반복자이다.

 

*외부반복자

장점

  1. 순회 제어 용이성: 개발자가 컬렉션을 순회하는 방식을 세밀하게 제어할 수 있습니다. 특정 요소에 도달했을 때 순회를 중단하거나 조건에 맞는 요소만 처리하는 등의 제어가 가능합니다.
  2. 컬렉션의 추상화와 독립적: 반복자는 컬렉션의 내부 구조를 몰라도 순회할 수 있어 컬렉션의 구현 방식에 독립적입니다. 이는 다양한 컬렉션에서 일관된 방식으로 요소를 접근할 수 있게 합니다.
  3. 읽기와 삭제 지원: Iterator는 요소를 읽고 삭제하는 작업을 안전하게 수행할 수 있도록 설계되어 있어, 컬렉션을 조작하면서 순회할 때 유용합니다.

단점

  1. 병렬 처리 비효율: 외부 반복자는 주로 순차적으로 데이터를 처리하기 때문에, 병렬 처리가 필요한 상황에서는 비효율적입니다.
  2. 코드 가독성 저하: 외부 반복자는 while이나 for문과 함께 사용되므로 코드가 다소 길어질 수 있습니다. 특히 조건문과 함께 사용할 경우 가독성이 떨어질 수 있습니다.
  3. 기본 컬렉션 수정 시 비정상 동작: Iterator를 통해 컬렉션을 순회하는 중간에 외부에서 컬렉션을 수정하면 ConcurrentModificationException이 발생할 수 있습니다.

 

외부반복자

 

외부 반복자일 경우 컬렉션 요소를 외부로 가져오는 코드와 처리코드가 모두 있어야 하지만 내부 반복자는 코드에서 제공한 데이터 처리 코드(람다식)를 가지고 컬렉션 내부에서 요소를 반복 처리한다.

 

 

*내부 반복자

내부반복자는 컬렉션이 제공하는 메서드를 사용하여 컬렉션 내부에서 요소를 자동으로 순회하는 방식이다.

forEach 메서드나 스트림 API가 내부 반복자의 대표적인 예시이다.

 

장점

  1. 코드 간결성: 반복 로직이 간결해지며, 람다식과 함께 사용해 가독성이 높아집니다.
  2. 병렬 처리 최적화: Stream API 등에서 내부 반복자는 병렬 처리를 쉽게 구현할 수 있어 성능 향상에 유리합니다.
  3. 명시적 반복 제어 불필요: 컬렉션 내부에서 반복을 처리하므로 next()나 hasNext() 같은 반복 제어 코드를 작성하지 않아도 됩니다.

단점

  1. 순회 제어의 제한: 내부 반복자는 순회를 중간에 멈추거나 특정 조건에서만 순회하는 등 세밀한 제어가 어렵습니다.
  2. 컬렉션 조작 제한: 내부 반복자 사용 중에는 컬렉션 요소를 삭제하거나 변경하는 작업이 제한됩니다.
  3. 호환성 제한: 일부 구형 컬렉션에서는 내부 반복자를 지원하지 않거나 구현이 제한적일 수 있습니다.

 

 

 

내부반복자는 멀티코어 CPU를 활용하여 요소들을 분배시켜 병렬 작업을 처리할 수 있다.

하나씩 하나의 작업을 순차적으로 하는 외부 반복자보다는 효율적으로 반복시킬 수 있을 것이다.

일반적으로 속도면에서 내부반복자가 더 빠르다. 외부 반복자는 컬렉션과 코드사이에서 왔다갔다하는 지연시간이 있기때문이다.

 

*내부반복자의 특징 

1. 내부 반복자이므로 처리 속도가 빠르고 병렬처리가 가능하다.

2. 람다식으로 구성되어 간결하고 다양한 요소처리가 가능하다.

3.중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.

 

외부반복자는 루프의 제어권이 '개발자'

내부반복자는 루프의 제어권이 'Stream 내부'

 

 

 파이브 라인

 

 

스트림 파이프라인

 

위와 같이 스트림은 하나이상이 연결될 수 있다. 위와 같이 스트림이 서로 연결된 것을 스트림 파이프라인이라고 한다.

 

예시: 오리지널 스트림에는 동물리스트가 왔다고 가정하자 그럼

필터링 중간 스트림에서는 동물리스트 중에서 포유류인 동물 만을 필터링 할 수 있고  포유류 중에서도 개과 동물만을 필터링 할 수있다. 매핑 스트림에서는 보통 요소를 변환시키는 작업이 이루어진다.

 

 

 병렬 처리

 

public class ParaeeleStreamExample{
	public static void main(String[] args){
    	
        List<String> list = new ArrayList<>();
        for(int i=0; i < 5000; i++){
        	list.add("닉네임" + i);
        }
        
        //만약 리스트에 요소가 적다면 병렬처리보다는 그냥 직렬처리가 더 빠를 수 있다. 스트림을 만들어내는 것에도 시간이 들기때문
        Stream<String> stream = list.parallelStream(); //병렬처리를 지원하는 스트림 일반 stream이면 병렬처처리가 안됨
    	stream.forEach(name -> {
        	System.out.println(name + ": " + Thread.currentThread().getName()) //처리하는 스레드 이름도 같이 출력
        	    
            
        })
    }
}

 

위 코드의 결과로는 아래와 같이 출력된다.

 

코드 실행결과

 

보다시피 병렬처리이기 때문에 순차적으로 처리되지 않는다. 닉네임1부터 차례대로 나오지 않는 것을 볼 수 있다.

스레드 이름도 살펴보면 worker7, 14, 5 등등 여러개의 스레드를 만들어서 작업을 처리하는 것을 확인가능하다.