⌨️ Stream에 대해
코딩테스트를 준비 중에 점점 더 Stream에 대한 중요성을 깨달아 참고용으로 한번 정리합니다.
1. 개요
- 스트림은 배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해줌
- List, Set, Map, Array 등 다양한 Collection으로부터 스트림 생성 가능
- 데이터 소스를 다루는 메서드 제공
2. 스트림의 핵심 개념과 특징
2.1. 스트림의 도입 배경
기존 `for`문과 `Iterator`를 활용한 방식의 한계를 느낌.
- 코드가 길고 복잡해짐
// Iterator 사용
public class IteratorExam{
public static void main(String[] args){
List<Interger> intList = Arrays.asList(1,2,3,4,5);
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
int num = it.next();
System.out.print(num);
}
}
}
// print
12345
// Stream 사용
public class streamExam{
public static void main(String[] args){
List<Integer> intList = Arrays.asList(1,2,3,4,5);
Stream<Integer> stream = list.stream();
stream.forEach(System.out::print);
}
}
// print
12345
위 처럼 짧은 코드로는 큰 차이를 느낄 수 없을지도 모릅니다.
그러나 로직이 길어지고 복잡해질수록 스트림의 효율을 더욱 증가할 것입니다.
- 데이터를 각기 다른 방식으로 다뤄야 하는 불편함
예를 들어 배열은 Arrays.sort() / 리스트는 Collection.sort()를 사용해야 합니다. 이러한 경우에서 스트림을 사용하면, 데이터의 소스의 관계없이 같은 방식으로 데이터를 가공/처리할 수 있습니다.
2.2. 스트림의 특징
- 스트림 처리 과정은 생성, 중간 연산, 최종 연산 세 단계의 파이프라인으로 구성 가능
- 스트림은 원본 데이터 소스를 변경하지 않음.( Read-Only)
- 스트림은 일회용.
- 스트림은 내부 반복자.
3. 스트림의 중간 연산
제가 자주 활용했던 중간 연산을 정리해 보았습니다. ( 대중적이 아닌 저의 활용 빈도입니다.)
3.1. 필터링(filter(), distinct())
조건에 맞는 데이터들만을 정제하는 역할
filter(): 조건에 맞는 데이터를 정제하여 더 작은 컬렉션을 생성 -> 조건은 람다식을 사용하여 정의
distinct(): 중복제거
public class FilteringExample {
public static void main(String[] args) throws Exception {
List<String> numbers = Arrays.asList("1", "2", "3", "1", "4");
numbers.stream()
.distinct() //중복 제거
.forEach(element -> System.out.println(element));
System.out.println();
numbers.stream()
.filter(element -> element.startsWith("1")) // 1만 필터링
.forEach(element -> System.out.println(element));
System.out.println();
numbers.stream()
.distinct() //중복제거
.filter(element -> element.startsWith("1")) //1만 필터링
.forEach(element -> System.out.println(element));
}
}
// print
1
2
3
4
1
1
1
Collection의 특정 조건식을 만족하는 형태의 Collection 추출해서 문제 해결 가능
-> https://school.programmers.co.kr/learn/courses/30/lessons/120905
3.2. 매핑(map())
원하는 필드만 추출하거나 특정 형태로 변환 -> 조건은 람다식을 사용하여 정의
public class IntermediateOperationExample {
public static void main(String[] args)
{
List<Integer> list = Arrays.asList(1, 2, 3, 4);
// 각 요소에 3을 곱한 값을 반환
list.stream().map(number -> number * 3).forEach(System.out::println);
}
}
// 출력값
3
6
9
12
Collections 에 변화를 주어 결괏값을 출력하는 알고리즘 문제에 활용 가능
-> https://school.programmers.co.kr/learn/courses/30/lessons/120809
3.3. 정렬( sorted() )
정렬할 때 사용
// 정렬
public class IntermediateOperationExample {
public static void main(String[] args) {
// 동물들의 이름을 모아둔 리스트
List<Integer> numbers = Arrays.asList(1,3,4,2,5);
// 인자값 없는 sort() 호출
numbers.stream().sorted().forEach(System.out::println);
}
}
// 출력값
1
2
3
4
5
// 역순
public class IntermediateOperationExample {
public static void main(String[] args) {
// 동물들의 이름을 모아둔 리스트
List<Integer> numbers = Arrays.asList(1,3,4,2,5);
// 인자값 없는 sort() 호출
numbers.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
} // Comparator.reverseOrder() == Collections.reverseOrder() 상황에 맞게
// Comparator 는 내부 sort 조건을 오버라이딩 하여 활용 가능
}
// 출력값
5
4
3
2
1
위 같은 정렬은 배열의 최댓값을 구하는 알고리즘 문제 등 에서 활용 가능
-> https://school.programmers.co.kr/learn/courses/30/lessons/120847
4. 스트림의 최종 연산
4.1. 기본 집계(sum(), count(), average(), max(), min() )
4.2 ★ 요소 수집 ( collect() )
spring framwork 에서 DTO 만들 때 진짜 많이 씀
1:N 관계에서 N 을 List로 collect.toList처럼
그 외 추가적인 Stream 활용법이나 고차 함수 활용 등은 Stream 공식 문서를 참고하고, 또 문제 풀이를 자주 접하여 익숙해져야겠습니다.
라이브러리라는 게 사실 쓰는 거만 쓰기 때문에 자주 안 쓰다 보면 또 까먹기 마련인 것 같습니다.
🌟REFERENCE
Stream 공식 문서 : https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html