💡 이번 챕터는 이것이 자바다 개정판으로 정리했다. 이 책에서는 "Chap17 스트림 요소 처리"에서 이번 단원을 다루고 있다.
스트림(Stream)은 컬렉션 및 배열의 요소를 반복 처리하기 위해 사용한다.
스트림은 요소들이 하나씩 흘러가면서 처리된다는 의미를 가지고 있다.
예를 들어, List 컬렉션에서 요소를 반복 처리하기 위해 스트림을 사용한다고 하면 아래와 같이 작성할 수 있다.
Stream<String> stream = list.stream();
stream.forEach( item -> //item 처리 );
ex) 스트림을 사용해 Set 컬렉션의 요소를 하나씩 읽고 출력해보자.
public class StreamExample {
public static void main(String... args) {
//Set 컬렉션 생성
Set<String> set = new HashSet<>();
set.add("홍길동");
set.add("김길동");
set.add("이길동");
//Stream을 이용한 요소 반복 처리
Stream<String> stream = set.stream(); //스트림 얻기
stream.forEach(name -> System.out.println(name)); //요소 처리 (람다식)
}
}
💡 Stream은 Iterator와 비슷한 반복자이지만, 다음과 같은 차이점을 가지고 있다.
- Stream은 내부 반복자이므로, 처리속도가 빠르고 병렬 처리에 효율적이다.
- Stream은 람다식으로 다양한 요소 처리를 정의할 수 있다.
- Stream은 중간 처리와 최종 처리를 수행하도록 파이프 라인을 형성할 수 있다.
스트림은 요소 처리 방법을 컬렉션 내부로 주입시켜서 요소를 반복 처리하는데, 이것을 내부 반복자라고 한다.
반대로, for문과 Iterator는 컬렉션의 요소를 컬렉션 바깥쪽으로 반복해서 가져와 처리하는데, 이것을 외부 반복자라고 한다.
내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있다.
하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있다는 장점이 있다.
ex) List 컬렉션의 내부 반복자를 이용해 병렬 처리를 해보자.
parallelStream()
= 병렬 처리 스트림을 얻는다.forEach()
= 요소 처리 방법인 람다식을 제공한다.public class ParallelStreamExample {
public static void main(String... args) {
//List 컬렉션 생성
List<String> list = new ArrayList<>();
list.add("홍길동");
list.add("김길동");
list.add("신길동");
list.add("오길동");
list.add("이길동");
//병렬 처리
Stream<String> parallelStream = list.parallelStream(); //병렬 스트림 얻기
parallelStream.forEach(name -> { //람다식: 요소 처리 방법
System.out.println(name + ": " + Thread.currentThread().getName());
});
}
}
신길동을 중심으로 2개씩 나뉘어 병렬 처리된 결과를 확인할 수 있다.
스트림은 하나 이상 연결될 수 있다. ⇒ 스트림 파이프 라인
아래 그림과 같이, 컬렉션의 오리지널 스트림 뒤에 필터링 중간 스트림이 연결될 수 있고, 그 뒤에 매핑 중간 스트림이 연결될 수 있다. 이와 같이 스트림이 연결되어 있는 것을 스트림 파이프라인(piplines)이라고 한다.
이것을 코드로 표현하면 다음과 같다.
//Student 스트림
Stream<Student> studentStream = list.stream();
//score 스트림
//Student객체를 getScore() 메소드의 리턴값으로 매핑
IntStream scoreStream = sudentStream.mapToInt(student -> student.getScore());
//평균 계산
double avg = scoreStream.average().getAsDouble();
mapToInt()
= 객체를 int로 매핑해서 IntStream으로 변환시킨다. (람다식)💡 메소드 체이닝 패턴을 이용하면 앞의 코드를 더 간결하게 작할 수 있다.
double avg2 = list.stream()
.mapToInt(student -> student.getScore())
.average()
.getAsDouble();
⚠️ 스트림 파이프라인으로 구성할 때 주의할 점은 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 있어야 한다는 것이다 !
※ 만약 최종 처리가 없다면 오리지널 및 중간 처리 스트림은 동작하지 않는다.
public class Student {
private String name;
private int score;
public Student (String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {return name;}
public int getScore() {return score;}
}
public class StreamPipeLineExample {
public static void main(String... args) {
List<Student> list = Arrays.asList(
new Student("홍길동", 10),
new Student("김길동", 20),
new Student("이길동", 30)
);
//방법 1 - 오리지널-중간-최종
Stream<Student> studentStream = list.stream();
IntStream scoreStream = studentStream.mapToInt(student -> student.getScore());
double avg1 = scoreStream.average().getAsDouble();
System.out.println("avg1 = " + avg1);
//방법 2 - 메소드 체이닝 패턴 이용
double avg2 = list.stream()
.mapToInt(student -> student.getScore())
.average()
.getAsDouble();
System.out.println("avg2 = " + avg2);
}
}
java.util.stream
패키지에는 스트림 인터페이스들이 있다. BaseStream에는 모든 스트림에서 사용할 수 있는 공통 메소드들이 정의되어 있다.
- Stream은 객체 요소를 처리하는 스트림이다.
- IntStream, LongStream, DoubleStream은 각 기본 타입인 int, long, double 요소를 처리하는 스트림이다.
주로 컬렉션과 배열에서 얻지만, 다음과 같은 리소스로부터 스트림 구현 객체를 얻을 수도 있다.
java.util.Collection
인터페이스는 스트림과 parallelStream() 메소드를 가지고 있기 때문에 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 컬렉션에서 객체 스트림을 얻을 수 있다.