Stream의 중간 연산

Drumj·2023년 2월 23일
0

오늘의 학습

바로 이전 포스팅과 이어서
이번엔 Stream 의 연산에 대해서 알아보자!

크게 중간 연산최종 연산으로 나누고 그 안에서 자세히 한번 알아보도록 한다.


스트림의 연산

중간 연산 : 연산 결과를 스트림으로 반환하기 때문에 중간 연산을 연속해서 연결 할 수 있다.
최종 연산 : 스트림의 요소를 소모하면서 연산을 수행하므로 단 한번만 연산이 가능하다.

스트림 연사을 나눠서 파악해보도록 하자! 각 연산의 반환타팁에 집중해보자.

String[] strArr = {"dd", "aaa", "CC", "cc", "b"};
Stream<String> stream = Stream.of(strArr); // 문자열 배열이 소스인 스트림

Stream<String> filterStream = stream.filter(); //걸러내기 (중간 연산)
Stream<String> distinctStream = stream.distinct(); //중복제거 (중간 연산)
Stream<String> sortedStream = stream.sorted(); //정렬 (중간 연산)
Stream<String> limitStream = stream.limit(5); // 스트림 자르기 (중간 연산)

int count = (int) stream.count(); //요소 개수 세기 (최종 연산)

마지막 최종 연산이 int 타입인 것을 확인할 수 있다.
책에서는 int로 캐스팅 되어있지 않은데 실제 인텔리제이에서 코드를 작성하니 기본으로 long타입이고 int로 바꿔줬기 때문에 캐스팅 하라고 알려준다.

각 연산 별로 어떤 것들이 있는지 알아보고 밑에서 자세히 알아보자.

중간 연산은 map()flatMap()이 핵심이라 한다.


최종 연산은 reduce()collect()가 핵심!


--중간 연산--

1. skip(), limit()

스트림의 일부를 잘라낼 때 사용.
skip() : 앞에서부터 n개 건너뛰기
limit() : maxSize 이후의 요소는 잘라냄

Stream<T> skip(long n)
Stream<T> limit(long maxSize)

IntStream intStream = IntStream.rangeClosed(1, 10); // 1,2,3,4,5,6,7,8,9,10
intStream.skip(3).limit(5).forEach(System.out::print); //45678

2. filter(), distinct()

스트림의 요소 걸러내기

Stream<T> filter(Predicate<? super T> predicate) //조건에 맞지 않는 요소 제거
Stream<T> distinct() // 중복 제거

//중복제거
IntStream intStream = IntStream.of(1,2,2,3,3,3,4,5,5,6);
intStream.distinct().forEach(System.out::print); //123456

//조건 filter
IntStream intStream = IntStream.rangeClosed(1, 10);
intStream.filter(i -> i%2==0).forEach(System.out::print); // 246810

//조건 여러개
intStream.filter(i -> i%2 !=0 && i%3 !=0).forEach(System.out::print);
intStream.filter(i -> i%2 !=0).filter(i -> i%3 !=0).forEach(System.out::print);

3. sorted()

스트림을 정렬할 때

Stream<T> sorted() // 스트림 요소의 기본 정렬(Comparable)로 정렬
Stream<T> sorted(Comparator<? super T> comparator) // 지정된 Comparator로 정렬

  • Comparator의 comparing()으로 정렬 기준을 제공
comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)

studemtStream.sorted(Comparator.comparing(Student::getBan)) //반 별로 정렬
			 .forEach(System.out::println);

정렬기준이 더 필요할 경우는 thenComparing()을 사용하며 된다.

studemtStream.sorted(Comparator.comparing(Student::getBan)) //반별
					.thenComparing(Student::getTotalScore);// 총점수별
                    .thenComparing(Student::getName);//이름별
                    .forEach(System.out::println);

4. map()

스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야할 때 사용.

매개변수 T타입을 R타입으로 변환해서 반환하는 함수를 지정해야 한다.

Stream<R> map(Function<? super T,? extends R> mapper)

//Stream<File> -> Stream<String> 예제
Stream<File> fileStream = Stream.of(new File("Ex1.java"),
									new File("Ex1"),
                                    new File("Ex1.bak"),
                                    new File("Ex2.java"),
                                    new File("Ex1.txt"));

Stream<String> fileNameStream = fileStream.map(File::getName);
fileNameStream.forEach(System.out.println); // 스트림의 모든 파일의 이름을 출력

.map()을 사용해서 Stream<File> -> Stream<String>으로 변환 했다!

예제도 설명이 아주 잘 되어 있어서 예제를 보는 걸 추천한다.
가장 중요한 것은 <File>에서 <String>으로 변환 되었다는 것이다.


5. peek()

forEach()와 비슷하지만 peek()는 최종 연산이 아닌 중간 연산이다!

Stream<T> peek(Consumer<? super T> action) //중간 연산(스트림을 소비하지 않음
void forEach(Consumer<? super T> action) //최종 연산(스트림을 소비)

filter()map()의 결과를 확인할 때 유용하게 사용된다.

//peek 확인해보기
fileStream.map(File::getName)     // Stream<File> → Stream<String>
		.filter(s -> s.indexOf('.')!=-1) // 확장자가 없는 것은 제외
		.peek(s -> System.out.printf("filename=%s%n", s)) // 파일명을 출력
		.map(s -> s.substring(s.indexOf('.')+1)) // 확장자만 추출
		.peek(s -> System.out.printf("extension=%s%n", s)) // 확장자만 출력
		.map(String::toUpperCase)     // 모두 대문자로 변환
		.distinct()                   //  중복 제거
		.forEach(System.out::print);

책의 예제 14-6에 peek을 추가 한 것이다. 실행이 잘 됐는지 확인해 볼 수 있다.


6. flatMap()

이 부분이 조금 어렵다. 말이 계속 스트림의 스트림. 스트림의 스트림. 스트림의 요소가 스트림. 이렇게 스트림쇼를 한다. 이제 잘 정리 해보자.

스트림의 타입이 <T[]> 인 경우 <T>로 반환해서 하면 더 편리할 때가 있다고 한다.
이럴때 flatMap()을 사용!

자 위의 사진을 순서대로 잘 살펴보자.
스트림을 생성 할때 잘 보니 스트림의 요소가 배열인 것을 확인 할 수 있다.
이걸 map()을 이용해서 변환을 해보니 Steam<Stream<String>>으로 바뀐걸 볼 수 있다...
이게 스트림의 요소 하나가 또!!! 스트림이 되었다는 것이다.
사진에서 1번이 가장 큰 스트림 그 안에 작은 스트림 2,3,4 번이 들어있는 것이다....ㅠㅠ

여기서 원한 것은 작은 스트림 2,3,4번이 하나의 문자열 배열이 되어서 큰 하나의 스트림 5번이 되기를 원했던 것이다. (여러 문자열 배열을 하나의 배열로 합치고 싶어~!)

이때는 그냥 map()이 아닌 flatMap()을 사용하기만 하면 된다고 한다!

마지막 파란 박스를 잘 보자. 위의 코드와 딱 하나 map() -> flatMap()으로 바뀐 것.
그렇게되니 타입 또한 Stream<String>으로 간단하게 하나의 스트림으로 바뀐 것을 볼 수 있다.

말이 자꾸 스트림의 요소가 스트림, 스트림의 스트림, 스트림 스트림 스트링.... (아 어지러워;;)

결국 하나만 기억하면 되는 것 같다.

여러 배열을 하나의 배열 스트림으로 만들고 싶을때는 flatMap()을 써라!!
스트림은 어려워


중간 연산의 마무으리~

꽤 많은 스트림의 중간 연산에 대해 알아봤다.
다양한 녀석들이 있었고 어떻게 결과가 나오는지 알았다.
이제서야 옛날옛적에 프로젝트에서 팀원들이 사용했던 stream에 대해 한 20% 정도 이해한 것 같다.
이제 최종 연산에 앞서 Optional<T> 에 대해 공부하고 스트림을 마무리 잘 해보자!!

아자리!!!!

0개의 댓글