[Java] 자바 스트림 Stream (map, filter, sorted / collect, foreach) 정리
오늘은 자바에서 쓰이는 스트림에 관해 정리해보려고 한다.
Stream은 자바 8부터 사용할 수 있다.
기존에 자바 컬렉션이나 배열의 원소를 가공할 때 for문, foreach 등으로 원소 하나씩 골라내서 가공했다면,
Stream을 이용해 람다 함수 형식으로 간결하게 요소들의 처리가 가능하다.
Stream이란!
스트림의 사전적 의미는 '흐르다' 또는 '개울'로 프로그래밍에서의 스트림도 사전적 의미가 크게 다르지 않다.
여기서는 '데이터의 흐름'을 뜻한다.
stream을 설명하기에 정말 적합한 사진이 있어서 출처란에 기입한 블로그 작성자 분의 이미지를 가져왔다. (짱짱)
물고기와 같은 어류의 이동처럼 Stream도 이에 비유하여 이해할 수 있다.
먼저 어부들이 그물망으로 잡고 싶은 물고기들을 잡는다. 이 행위를 filter라고 하며, 중간 연산자라고 한다.
그리고 이 물고기들을 포장하여 판매해야하기에 상자에 담는데, 이 행위를 map이라고 한다. 이 또한 중간 연산자라고 한다.
마지막으로 상자들을 운반하여 다른 곳으로 이동하며 끝난다. 이 행위를 collect라고 하며 이 연산자는 최종 연산자라고 한다.
이처럼 스트림은 수많은 데이터 흐름 속 각각의 원하는 값을 가공하여 최종 소비자에게 제공하는 역할을 한다.
Stream의 특징
스트림 내 요소들에 대해 함수가 적용된 결과의 새로운 요소로 매핑한다.
기능적인 측면에서 스트림은 컬렉션(배열포함)의 저장 요소를 하나씩 참조해 람다식으로 처리할 수 있도록 해주는 반복자이다.
사실 우리는 반복자를 스트림이 아니더라도 계속 사용해왔다. (ex. Iterator 반복자 ,,)
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
int num = it.next();
System.out.println(num);
}
}
상단의 코드는 Iterator 반복자를 사용한 예시로, 정수가 있는 리스트를 하나씩 순회하면서 값을 출력한다.
이를 스트림으로 바꿔보면,
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
Stream<Integer> stream = list.stream();
stream.forEach(System.out::println);
}
코드가 매우 간결해진 것을 확인해볼 수 있다.
이제 본격적으로 스트림의 특징에 대해 알아보겠다.
- 람다식으로 요소 처리 코드를 제공한다.
- 내부 반복자를 사용하므로 병렬 처리가 쉽다. (Iterator의 경우 외부 반복자)
- 중간 처리와 최종 처리가 존재한다.
- 중간 처리 (매핑, 필터링, 정렬) / 최종 처리 (반복, 카운팅, 평균, 총합 등 집계 처리)
배열의 원소를 가공하는데 있어 map, filter, sorted 등이 있다.
map
요소들을 특정 조건에 해당하는 값으로 변환
요소들을 대/소문자 변형 등의 작업을 하고 싶을 때 사용 가능
filter
요소들을 조건에 따라 걸러내는 작업을 해줌
길이의 제한, 특정 문자 포함 등의 작업을 하고 싶을 때 사용 가능
sorted
요소들을 정렬해주는 작업
요소들의 가공이 끝났다면 리턴해줄 결과를 collect을 통해 만들어줌
Test Set
ArrayList<string> list = new ArrayList<>(Arrays.asList("Apple","Banana","Melon","Grape","Strawberry"));
System.out.println(list);
//[Apple, Banana, Melon, Grape, Strawberry]
Map
list.stream().map(s->s.toUpperCase());
list.stream().map(String::toUpperCase);
리스트의 요소들을 대문자로 변경한다.
요소들을 대문자로 가공하였다면 collect 를 이용하여 결과를 리턴받을 수 있고, forEach 를 이용하여 바로 출력해볼수 있다.
System.out.println(list.stream().map(s->s.toUpperCase()).collect(Collectors.joining(" "))); //APPLE BANANA MELON GRAPE STRAWBERRY
System.out.println(list.stream().map(s->s.toUpperCase()).collect(Collectors.toList())); //[APPLE, BANANA, MELON, GRAPE, STRAWBERRY]
System.out.println(list.stream().map(String::toUpperCase).collect(Collectors.toList())); //[APPLE, BANANA, MELON, GRAPE, STRAWBERRY]
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
//APPLE
//BANANA
//MELON
//GRAPE
//STRAWBERRY
filter
list.stream().filter(t->t.length()>5)
filter 는 요소를 특정 기준으로 걸러낼 수 있다.
요소의 크기가 5이상인 값만 뽑아낸다.
System.out.println(list.stream().filter(t->t.length()>5).collect(Collectors.joining(" "))); //Banana Strawberry
System.out.println(list.stream().filter(t->t.length()>5).collect(Collectors.toList())); //[Banana, Strawberry]
마찬가지로 filter로 가공한 결과를 얻을 수 있다!
Sorted
list.stream().sorted()
리스트의 요소를 정렬한다.
System.out.println(list.stream().sorted().collect(Collectors.toList())); //[Apple, Banana, Grape, Melon, Strawberry]
사용 예시
public class Human implements Comparable<Human> {
private Long idx;
private String name;
private Integer money;
private LocalDate birth;
private List<String> travelDestinations;
}
기본 사용법
@DisplayName("이름만 가져와서 List 만들기")
void mapTest1() {
List<String> humanNames = humans.stream()
.map(h -> h.getName())
.collect(Collectors.toList());
for (String humanName : humanNames) {
System.out.print(humanName + " ");
}
}
이렇게 자바 스트림의 개념, 사용하는 방법들에 대해 알아봤다.
References