Stream 파이프라인
스트림은 데이터의 필터링, 매핑, 정렬, 그룹핑 등의 중간 처리와 합계, 평균, 카운팅, 최댓값, 최솟값 등의 최종 처리를 파이프라인을 통해 해결한다. 여기서, 파이프라인은 컴퓨터 과학에서 한 데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태로 연결된 구조를 말한다. 최종 처리 스트림과 오리지널 스트림을 제외한 나머지 스트림은 중간 처리 스트림으로 볼 수 있다.
위의 그림을 보면 알 수 있듯이, 이전 데이터 처리의 출력이 다음 단계의 입력으로 이어진다. 좀 더 구체적으로 말하자면, 스트림 인터페이스에는 필터링, 매핑, 정렬 등의 많은 중간 처리 메소드가 있는데, 이 메소드들은 중간 처리된 '스트림'을 반환한다. 그리고 이 스트림에서 다시 중간 처리 메소드를 호출해서 파이프라인을 형성하게 된다.
스트림 파이프라인에는 한 가지 중요한 특징이 있다. 바로 지연(lazy)이다.
중간 스트림이 생성될 때 요소들이 바로 중간 처리되는 것이 아니라 최종 처리가 시작 되기 전까지 중간 처리는 지연된다. 즉, 최종 처리가 시작이 되어야 비로소 컬렉션의 요소가 하나씩 중간 스트림에서 처리가 되고 최종 처리까지 오게 되는 것이다.
아래는 회원 컬렉션에서 남자만 필터링하는 중간 스트림을 연결하고, 다시 남자의 나이로 매핑하는 스트림을 연결한 후, 최종 남자 평균 나이를 집계하는 파이프라인을 나타낸 것이다.
코드로 살펴보면 다음과 같다.
List<Member> list = Arrays.asList(
new Member("이산", Member.MALE, 30),
new Member("진영", Member.MALE, 26),
new Member("별찬", Member.MALE, 23),
new Member("민주", Member.FEMALE, 21)
);
Stream<Member> maleFemaleStream = list.stream();
Stream<Member> maleStream = maleFemaleStream.filter(m -> m.getSex() == Member.MALE);
IntStream ageStream = maleStream.mapToInt(Member::getAge);
OptionalDouble optionalDouble = ageStream.average();
double ageAvg = optionalDouble.getAsDouble();
System.out.println(ageAvg);
코드를 이해할 필요는 없고, 위 코드가 가장 원초적인 스트림 파이프라인의 코드라는 정도만 알고 넘어가면 된다. 지역 변수를 하나 하나 정의를 하였고, 그 다음 지역 변수를 정의를 할 때 이전에 사용한 지역 변수에서 메소드를 호출하는 방식으로 사용하였다.
이제, 위 코드의 길이를 줄여보자.
List<Member> list = Arrays.asList(
new Member("이산", Member.MALE, 30),
new Member("진영", Member.MALE, 26),
new Member("별찬", Member.MALE, 23),
new Member("민주", Member.FEMALE, 21)
);
double ageAvg = list.stream()
.filter(m -> m.getSex() == Member.MALE)
.mapToInt(Member::getAge)
.average()
.getAsDouble();
System.out.println(ageAvg);
지역 변수만 모두 생략하고 연결하면 위와 같은 형태가 된다.
- filter() 메소드를 통해 남자 Member 객체를 요소로 하는 새로운 스트림을 생성
- mapToInt() 메소드를 통해 Member 객체를 age 값으로 매핑해서 age를 요소로 하는 새로운 스트림을 생성
- average() 메소드는 age 요소들의 평균을 OptionalDouble에 저장
- OptionalDouble에서 저장된 평균값을 읽기 위하여 getAsDouble() 메소드를 호출
참고