Stream의 최종 연산

Drumj·2023년 2월 24일
0

오늘의 학습

이제 스트림의 생성 - 스트림의 중간연산을 정리했으니
오늘은 스트림의 최종 연산에 대해 정리를 해보자!!!
바로 시작~!


--최종 연산--

1. forEach(),forEachOrdered()

중간 연산의 peek() 와는 달리 스트림의 요소를 소모하는 최종 연산이다.
반환 타입이 void이므로 스트림의 요소를 출력하는 용도로 많이 사용한다.

void forEach(Comsumer<? super T> action) //병렬스트림인 경우 순서 보장 X
void forEachOrdered(Comsumer<? super T> action) //병렬스트림인 경우 순서 보장 O

앞에서도 자주 사용했으니 빠르게 넘어가자


2. 조건검사

  • 조건 검사 : allMatch,antMatch,noneMatch
    지정된 조건에 요소가 어떻게 일치 되는지 확인하는 메서드 들이다.
boolean allMatch(Predicate<? super T> predicate) // 모든 요소가 일치하면 참
boolean anyMatch(Predicate<? super T> predicate) // 하나의 요소라도 일치하면 참
boolean noneMatch(Predicate<? super T> predicate) // 모든 요소가 불일치하면 참

//예제 총점 100점 이하인 학생이 한명이라도 있는가
boolean noFailed = stuStream.anyMatch(s -> s.getTotalScore() <= 100)
  • 조건에 일치하는 요소 찾기 : findFirst,findAny
Optional<T> findFirst() // 조건에 일치하는 첫 번째 요소를 반환
Optional<T> findAny() //조건에 일치하는 요소를 아무거나 하나 반환.(병렬 스트림)

결과가 null일 수 있어서 Optional 을 사용한다.
위의 녀석들은 filter()와 주로 같이 사용한다고 한다.

//예제
Optional<Student> stu = stuStream
	.filter(s -> s.getTotalScore() <= 100).findFirst(); // 순차 스트림

Optional<Student> stu = parallelStream
	.filter(s -> s.getTotalScore() <= 100).findAny(); //병렬 스트림

3. reduce()

최종 연산중에 제일 중요하다고 한다!!

  • 스트림의 요소를 하나씩 줄여가며 누적연산 수행
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity,BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)
  • indentity - 초기값
  • accumulator - 이전 연산결과와 스트림의 요소에 수행할 연산
  • combiner - 병렬처리된 결과를 합치는데 사용할 연산(병렬 스트림)

이 중에서도 indentityaccumulator가 아주 중요하다.
combiner는 병렬 스트림에서 사용하기 때문에 추후에 다루도록 하겠다.

최종 연산 count() 와 sum()은 내부에 모두 reduce()를 이용한다고 한다. max와 min은 생략하겠다.

int count = intStream.reduce(0,(a,b) -> a+1);
int sum = intStream.reduce(0,(a,b) -> a+b);

초기값으로 설정된 0을 a에 넣어서 연산을 수행한다고 보면 된다!
b는 스트림의 요소. 코드로 이해해보도록 하자!

//reduce() 작동 알아보기
int a = identity;

for (int b : stream) a = a + b; //sum()

책과 영상의 예제를 보면서 더 쉽게 파악할 수 있다!


4. collect()와 Collectors

  • collect()Collector를 매개변수로 하는 스트림의 최종 연산
Object collect(Collector collector)
//Collector를 구현한 클래스의 객체를 매개변수로 사용, Collector는 인터페이스다.

reduce와 collect

  • reduce() : 스트림 요소 전체를 리듀싱
  • collect() : 그룹별로 나눠서 리듀싱 가능
  • Collector는 collect에 필요한 메서드를 정의해 놓은 인터페이스

    여기서도 supplier()accumulator()가 핵심이다.

  • Collectors 클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공

Collector 인터페이스를 직접 구현할 필요 없이 Collectors 클래스를 잘 사용하면 된다! Collectors가 구현해서 우리에게 제공하기 때문!!

collect() : 스트림의 최종연산, 매개변수로 밑에 Collector를 필요로 함
Collector : 인터페이스, collect를 사용하려면 이걸 구현해서 사용해야한다,
Collectors : 클래스, Collector를 구현한! 그래서 이걸 잘 사용하면 된다.


4-1. 컬렉션, 배열로 변환

  • 스트림을 컬렉션으로 변환 - toList(), toSet(), toMap(), toCollection()
    이 녀석들은 모두 Collectors가 제공한다.
List<String> names = stuStream.map(Student::getName) //Stream<Student> -> Stream<String>
					.collect(Collectors.toList()); //Stream<String> -> List<String>
                    
ArrayList<String> list = names.stream()
	.collect(Collectors.toCollection(ArrayList::new)); //Stream<String> -> ArrayList<String>
                    
Map<String,Person> map = personStream
	.collect(Collectors.toMap(p -> p.getRegId(), p -> p)); //Stream<Person> -> Map<String,Person>
  • 스트림을 배열로 변환 - toArray()
Student[] stuNames = studentStream.toArray(Student[]::new); //OK 이 방법을 많이 사용한다.
Student[] stuNames = studentStream.toArray();  // 에러
Object[] stuNames = studentStream.toArray(); //OK

toArray에 매개변수가 없다면 반환타입을 Object[]로 해야 에러가 없다!


4-2. 통계 counting(), summungInt()

단순하게 count(), sum()을 사용해도 되는데 왜??collect(Collerctors.counting())을 사용할까???

앞에서도 말했듯이 collect()를 사용하게 되면 그룹별로 어떤 행위를 할 수 있게 된다. 그렇다면 여기서 설명하는 모든 것들이 그룹별로 처리하기 위해서 존재한다고 볼 수 있다.

코드를 비교해서 알아보자.

long count = stuStream.count(); // 스트림 전체를 카운트
long count = stuStream.collect(Collectors.counting()); //그룹별로 카운팅 가능.
//하지만 여기서는 그룹을 나누지 않았기 때문에 위 두 코드의 결과는 똑같다!

//여기서 부터는 Collectors. 이 스태틱 임포트(static import)되었다고 가정하고 생략하겠다.
long totalScore = stuStream.mapToInt(Student::getTotoalScore).sum();
long totalScore = stuStream.collect(summingInt(Student::getTotalScore));
//여기도 마친가지로 그룹별 합산 가능!! 이후에 groupingBy()를 배우도록 하자.

이 외에 maxBy나 summarizingInt 는 생략.....! ㅎㅎㅎ


4-3. 리듀싱 reducing()

이 녀석도 마찬가지로 Collerctors가 제공한다!!
사용방법이나 하는 일은 reduce()와 동일하다고 생각하면 된다!
근데 왜?? collect()랑 쓰냐???? 여기도 마찬가지고 그룹별로 리듀싱하기 위해서이다.

Collector reducing(BinaryOperator<T> op)
Collector reducing(T identity,BinaryOperator<T> op) //가장 많이 사용
Collector reducing(T identity, Function<T,U> mapper, BinaryOperator<T> op) // map + reduce
OptionalInt max = intStream.reduce(Integer::max);
Optional<Integer> max = intStream.boxed().collect(reducing(Integer::max));

간단하게 코드만 보고 넘어가도록 하겠다.


4-4. 문자열 스트림의 요소를 모두 연결 joining()

문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환한다.
구분자,접두사,접미사를 지정할 수 있다!

스트림의 요소가 문자열일때만 가능하기때문에 문자열이 아니라면 map을 먼저 해준다!
코드로 알아보고 넘어가도록 하자!

String studentName = stuStream.map(Student::getName) //학생 이름만 뽑아
						.collect(Collectors.joining()); //하나로 반환.
String studentName = stuStream.map(Student::getName)
						.collect(Collectors.joining(",")); // 이름을 ","로 구분!
String studentName = stuStream.map(Student::getName)
						.collect(Collectors.joining(",","[","]")); // [이름,이름] 처럼 출력
                        
//예상 출력 결과
1. 이름1이름2이름3이름4
2. 이름1,이름2,이름3,이름4
3. [이름1,이름2,이름3,이름4]
//이렇게 출력되지 않을까?

4-5. 스트림의 그룹화와 분할

여기서 설명할 것들도 모두 Collectors에 있는 메서드들이다!

  • partitioningBy() 는 스트림을 2분할 한다.
  • groupingBy() 는 스트림을 n분할 한다.
Collector partitioningBy(Predicate predicate)
Collector partitioningBy(Predicate predicate, Collector downstream)

Collector groupingBy(Function classifier)
Collector groupingBy(Function classifier, Collector downstream)
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)

메서드가 여러개 있는데 매개변수는 신경쓰지 말고 2분할, n분할 한다는 것만 기억하면 된다고 한다.

앞에서 계속 말했던 그룹화가 드디어 여기서 나오는 것이다!
collect()partitioningBy()groupingBy()와 함께 쓴다고 한다.


4-6. partitioningBy()

이제 하나씩 자세히 알아보자!
Collectors.는 스태틱 임포트!! (했다고 가정)

Map<Boolean, List<Student>> stuBySex = stuStream
		.collect(partitioningBy(Student::isMale)); //학생들을 성별로 분할
List<Student> maleStudent = stuBySex.get(true); //Map 에서 남학생 목록을 얻는다.
List<Student> femaleStudent = stuBySex.get(false); //Map 에서 여학생 목록을 얻는다.

이렇게 남 녀 로 간단하게 나눠볼 수 있다! 당연히 groupingBy()를 사용해도 되지만 단순히 2분할을 할거면 partitioningBy()를 사용하는게 더 빠르다.

그리고 이렇게 나눈 다음에 counting()이나 summingInt()등을 사용할 수 있다!

//분할하고 카운트를 해보자
Map<Boolean, Long> stuNumBySex = stuStream
		.collect(partitioningBy(Student::isMale),counting()); //분할 + 통계
System.out.println("남학생 수 :" + stuNumBySex.get(true)); //남학생 수
System.out.println("여학생 수 :" + stuNumBySex.get(false)); //여학생 수

4-7. groupingBy()

기본적으로 위의 partitioningBy()와 같다!! 단순 그룹화와 다중 그룹화를 알아보자!

Map<Integer, List<Student>> stuByBan = stuStream
		.collect(groupingBy(Student::getBan, toList())); //toList() 생략가능

//다중 그룹화
Map<Integer, Map<Integer, List<Student>>> stuByHakAndBan = stuStream
		.collect(groupingBy(Student::getHak, //학년별
        		 groupingBy(Student::getBan) //반별
		));

여기에 map을 활용하여 더 복잡하게 사용할 수 있다고 한다.
이 부분은 영상을 참고하는게 더 좋을 것 같다.


스트림의 변환

변환 방법이 어려운게 아니라 언제 어떤 메서드를 써야하는지 찾아보는게 어려웠다고 하신다. 그래서 이렇게 표로 잘 정리를 해놓았으니 참고하라고!!!
그저 빛...



마무리

이렇게 Stream의 최종 연산에 대해서도 알아봤다!
다른거 쉽게 이해할 수 있었고 reduce도 나쁘지 않았다!
하지만 collect가 시작되면서 다양한 메서드들을 알아보면서 조금 복잡해 지기 시작했다. 책의 예제가 정리가 잘되어 있으니 언제든지 살펴보면서 공부하면 좋을 것 같다!!

collect(Collertors impl Collector)
이렇게 collect()를 사용하려면 Collector를 구현한 Collectors 클래스를 사용!

해야 한다는 것만 잘 기억하면 좋을 것 같다!!

0개의 댓글