본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석11-5 스트림의 연산(최종연산)

by 이쟝 2022. 2. 2.

스트림의 연산(중간연산, 최종연산)

중간연산 최종연산
n 1
Stream을 반환 결과를 반환(int, Boolean ..) 스트림의 요소를 소모해서
최종연산 후에는 스트림이 닫히게 되고 더이상 사용 불가

 

최종 연산 설명
void forEach(Consumer<? super T> action)
void forEachOrdered(Consumer<? super T> action)
(
순서유지, 병렬스트림)
각 요소에 지정된 작업 수행
long count( ) 스트림의 요소의 개수 반환
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
스트림의 최대값/최소값을 반환(정렬기준)
Optional<T> findAny( ) // 아무거나 하나(병렬, filter)
Optional<T> findFirst( ) // 첫 번째 요소(직렬)
스트림의 요소 하나를 반환
boolean allMatch(Predicate<T> p)     // 모두 만족?
boolean anyMatch(Predicate<T> p)   // 하나라도 만족?
boolean noneMatch(Predicate<T> p) // 모두 만족하지 않는지?
주어진 조건을 모든 요소가 만족시키는지,
만족시키지 않는지 확인
Object[ ] toArray( )
A[ ] toArray(IntFUnction<A[ ]> generator) 
스트림의 모든 요소를 배열로 반환
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)
스트림의 요소를 하나씩 줄여가면서(리듀싱)계산한다.
ex) sum, count
R collect(Collector<T,A,R> collector)
R collect(Supplier<R> supplier, BiCosumer<R, T> accumulator,
BiConsumer<R,R> combiner)
스트림의 요소를 수집한다. 주로 요소를 그룹화하거나
분할한 결과를 컬렉션에 담아 반환하는데 사용된다.

-> reducecollect가 최종 연산의 핵심! 


1. forEach( )

- 스트림의 모든 요소에 지정된 작업을 수행 – forEach( ), forEachOrdered( )

- 반환 타입이 void이어서 스트림의 요소를 출력하는 용도로 많이 사용

- 중간연산 peek( )과 달리 스트림의 요소를 소모하는 최종연산

 


2. 조건검사

(1) 조건검사 - allMatch( ), anyMatch( ), noneMatch( )

(2) 조건에 일치하는 요소 찾기 – findFirst( ), findAny( )

  • 결과가 null일 수 있기 때문에 Optional<T>를 사용. 주로 filter( )와 함께 사용한다. 
  • findFirst()는 조건에 일치하는 첫 번째 것을 반환한다. 
  • findFirst와 findAny의 차이는 병렬스트림의 사용 유무이다.

(3) 스트림의 요소를 하나씩 줄여가며 누적연산 수행, 최종결과 반환 – reduce( )

  • 스트림의 요소를 줄여나가면서 연산을 수행하고 최종결과를 반환한다. 그래서 매개변수타입이 BinaryOperator<T>이다. 
  • 처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산한다.
  • 초기값(identity)와 어떤 연산(accumulator)으로 스트림의 요소를 줄여 나갈 것인지 정하면 됨

*reduce의 핵심*

- identity : 초기값

- accumulator : 이전 연산결과와 스트림의 요소에 수행할 연산

- combiner : 병렬 처리된 결과를 합치는데 사용할 연산(병렬 스트림)

 

-> 초기값이 있는 연산(T identity)과 없는 연산으로 나뉨

-> 초기값이 없을 땐 null이 있을 수도 있어서 Optional<T>를 반환

 

- max( )와 min( )의 경우, 초기값이 필요 없어서 Optional<T>를 반환하는 매개변수 하나짜리인 reduce( )를 사용하는 것이 낫다.

(intStream의 타입이 IntStream(기본형 스트림)일 경우OptionalInt)를 사용해야 한다. Stream<T>와 달리 IntStream(기본형)에 정의된 reduce( )반환타입이 OptionalInt이기 때문이다. 저장된 값을 꺼낼 때는 getAsInt()사용!!!

 

* reduce()가 동작하는 과정 *

int a = identity;  // a는 누적결과를 저장할 변수, 초기값을 a에 저장한다.

 

for(int b : stream)

    a = a + b;     // 모든 요소의 값을 a에 누적한다, 만약 count이면 b가 1로 바뀌게 됨


3. collect()와  Collectors 

- collect( )는 스트림의 요소를 수집하는 최종 연산으로 스트림의 요소를 수집하려면 어떻게 수집할 것인가에 대한 방법이 정의되어 있어야 한다. -> 이 방법을 정의한 것이 Collector 인터페이스

 

collect( ) Collector를 매개변수로 하는 스트림의 최종연산
Collector 인터페이스, 컬렉터는 이 인터페이스를 구현해야 한다.
Collector는 수집(collect)에 필요한 메서드를 정의해 놓은 인터페이스이다.
Collectors 클래스, static 메서드로 미리 작성된 컬렉터(Collector를 구현한 클래스)를 제공한다.

- collect()의 매개변수 타입은 Collector인데, 매개변수가 Collector를 구현한 클래스의 객체이어야 한다는 뜻이다. 

 

* Collector 인터페이스(인터페이스라서 원래 직접 다 구현해야 함) *

Stream의 요소인 TA를 누적한다(reduce와 동일) 그 다음 R로 변환해서 반환!

supplier( ) 작업 결과를 저장할 공간을 제공
accumulator( ) 스트림의 요소를 수집(collect)할 방법을 제공
(이전 연산결과와 스트림의 요소에 수행할 연산)
combiner( ) 두 저장공간을 병합할 방법을 제공(병렬 스트림)
finisher( ) 결과를 최종적으로 변환할 방법을 제공

 

* Collectors 클래스(Collect 인터페이스를 구현해 놓아서 직접 구현할 필요 없다.) *

 

(1) 스트림을 컬렉션, 배열로 변환

1. 스트림을 컬렉션으로 변환 – toList( ), toSet( ), toMap( ), toCollection( ) -> 모두 Collectors가 제공

-> StreamStudentstuStream에서 학생들 이름만 뽑아서 Stream<String>으로 만든 뒤 List<String>에 담은 것(학생 이름이 담긴 List생성!

 

-> ListSet이 아닌 특정 컬렉션을 지정하려면 toCollection( )에 해당컬렉션의 생성자 참조를 매개변수로 넣어주면 됨 (여기서는 반환결과가 ArrayList!)

 

-> map으로 지정하려면 toMap을 사용하면 된다.

-> mapkeyvalue를 저장해줘야 함(객체의 어떤 필드를 키로 사용할 지와 값으로 사용할지를 지정해줘야 함)

-> 요소의 타입이 Person인 스트림에서 keygetRegId(): 주민번호, value는 객체 자기자신

 

(2) 스트림을 배열로 변환 - toArray( )

- 스트림에 저장된 요소들을 ‘T[]’타입의 배열로 변환

-> 매개변수가 없을 때는 Object[ ]로 반환, 해당 타입의 생성자 참조를 매개변수로 지정해줘야 함

-> 람다식으로 변환하면?

Student[ ] stuNames = studentStream.toArray( ( i ) -> new Strudent[ i ] );

 

(3) 스트림의 통계

1. 스트림의 통계정보 제공 - counting( ), summingInt( ), maxBy( ), minBy( ), ….

  • 스트림의 요소가 몇 개인지 알 수 있다.
  • 전체 counting이 아닌 그룹별로 counting하기 위해서 collect를 사용
  • Collectors의 static메서드를 호출하면 Collectors.를 생략 가능. (import static java.util.stream.Collectors.counting;)
  • counting이외에도 다른 메서드를 호출하는 것도 가능

  • 학생스트림인 stuStream에서 총점을 다 더함
  • 전체 총합이 아닌 그룹별로 합계를 구하기 위해서 collect를 사용
  • import static java.util.stream.Collectors.summingInt;

    • 결과가 없을 수도 있으니까(null일 수도 있어서) OptionalInt<T>
    • 비교기준은 총점(StudentgetTotalScore)
    • 전체 요소중에서 최대값이 아닌 그룹별로 최대값을 구하기 위해서 collect를 사용

(4) 스트림을 리듀싱 reducing( )

reduce( ) Collectors.reducing( ) 둘 다 거의 동일
전체에 대한 reducing 그룹별로 나눠서 reducing 가능

 

  •  IntStream에는 매개변수 3개짜리 collect()만 정의되어 있어서 boxed()를 통해 IntStream을 Stream<Integer>로 변환해야 매개변수 1개짜리 collect( )를 사용할 수 있다.

(5) 문자열의 결합 - joining( )

  • 문자열 스트림의 모든 요소를 하나의 문자열로 연결해서 반환한다.
  • 스트림의 요소가 문자열이 아닌 경우에는 map( )을 먼저 이용해서 스트림의 요소를 문자열로 변환해야 한다.
  • 만약 map( )없이 스트림에 바로 joining( )하면, 스트림의 요소에 toString( )을 호출한 결과를 결합

 

예제)

 

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;

public class Ex14_10 {
	public static void main(String[] args) {
		
		Student2[] stuArr = {
				new Student2("이자바", 3, 300),
				new Student2("김자바", 1, 200),
				new Student2("안자바", 2, 100), 
				new Student2("박자바", 2, 150),
				new Student2("소자바", 1, 200),
				new Student2("나자바", 3, 290),
				new Student2("감자바", 3, 180) 
		};
		
		// 학생 이름만 뽑아서 List<String>에 저장
		List<String> names = Stream.of(stuArr).map(Student2::getName).collect(Collectors.toList());
		System.out.println(names);
		
		// 학생 이름만뽑아서 joining으로 연결
		String stuNames = Stream.of(stuArr).map(Student2::getName).collect(joining(" ","<",">"));
		System.out.println(stuNames);
		
		// 스트림을 배열로 변환
		Student2[] stuArr2 = Stream.of(stuArr).toArray(Student2[]::new);
		
		for(Student2 s:stuArr2) 
			System.out.println(s);
		
		// 스트링을 Map<String, Student>으로 변환 학생 이름이 key
		Map<String, Student2> stuMap = Stream.of(stuArr).collect(Collectors.toMap(s->s.getName(), p->p));
		
		for(String name: stuMap.keySet())
			System.out.println(name + "->" + stuMap.get(name));
		
		long count = Stream.of(stuArr).collect(counting());  // static java.util.stream.Collectors.*; 때문에 Collectors 생략가능
		System.out.println("count = " + count);
		
		long totalScore = Stream.of(stuArr).collect(summingInt(Student2::getTotalScore));
		System.out.println("totalScore = " + totalScore);
		
		totalScore = Stream.of(stuArr).collect(reducing(0, Student2::getTotalScore, Integer::sum));
		System.out.println("totalScore = " + totalScore);
		
		Optional<Student2> topStudent = Stream.of(stuArr).collect(maxBy(Comparator.comparingInt(Student2::getTotalScore)));
		System.out.println("topStudent = " + topStudent.get());
		
		IntSummaryStatistics stat = Stream.of(stuArr).collect(summarizingInt(Student2::getTotalScore));
		System.out.println(stat);
		
	}
}
class Student2 implements Comparable<Student2> {
	String name;
	int ban;
	int totalScore;
	
	Student2(String name, int ban, int totalScore) {
		this.name = name;
		this.ban = ban;
		this.totalScore = totalScore;
	}
	
	public String toString() {
		return String.format("[%s, %d, %d]", name, ban, totalScore).toString();
	}
	
	String getName() { return name; }
	int getBan() { return ban; }
	int getTotalScore() { return totalScore; }
	
	public int compareTo(Student2 s) {
		return s.totalScore - this.totalScore;
	}
}

 

더보기

(1) [이자바, 김자바, 안자바, 박자바, 소자바, 나자바, 감자바]

(2) <이자바 김자바 안자바 박자바 소자바 나자바 감자바>

(3)

[이자바, 3, 300]

[김자바, 1, 200]

[안자바, 2, 100]

[박자바, 2, 150]

[소자바, 1, 200]

[나자바, 3, 290]

[감자바, 3, 180]

(4)

안자바->[안자바, 2, 100]

김자바->[김자바, 1, 200]

박자바->[박자바, 2, 150]

나자바->[나자바, 3, 290]

감자바->[감자바, 3, 180]

이자바->[이자바, 3, 300]

소자바->[소자바, 1, 200]

(5) count = 7

(6) totalScore = 1420

(7) totalScore = 1420

(8) topStudent = [이자바, 3, 300]

(9) IntSummaryStatistics{count=7, sum=1420, min=100, average=202.857143, max=300}

 

-> Collectors가 생략될 수 있는 건 import static java.util.stream.Collectors.*;를 해줬기 때문이다.