[Java8] 스트림(Stream) 최종 연산자(a.k.a 수집기)

hyozkim·2020년 5월 7일
0

StreamAPI

목록 보기
4/4
post-thumbnail

최종 연산자(a.k.a 수집기)

1. count, sum, min, max, average

long counts = IntStream.of(1, 3, 5, 7, 9).count();  // 5
long sum = LongStream.of(1, 3, 5, 7, 9).sum();      // 25

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();// 1
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();// 9

DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
	.average()
	.ifPresent(System.out::println); // 3.3

스트림이 비어있는 경우 count, sum은 0을 출력하게 됩니다.
하지만 평균,최소,최대 경우에는 표현할 수 없기 때문에 Optional을 이용해 리턴한다.

2. reduce

// Integer Example
List<Integer> ages = Arrays.asList(1,2,3,4,5,6,7,8,9);

int result = ages.stream().reduce(0, (subtotal, element) -> subtotal + element); // 45 

int result2 = ages.stream().reduce(0, Integer::sum); // 45

// String Example
String[][] namesArray = new String[][] {
                {"a", "b"}, {"c", "d"},
                {"e", "f"}, {"g", "h"}
        };

String reduceStrResult = Arrays.stream(namesArray)
                .flatMap(innerArrays -> Arrays.stream(innerArrays))
                .reduce("", (partString, element) -> partString+element); // abcdefgh

reduce는 누적된 값을 계산한다.

Integer Example
subtotal+element 결과가 subtotal이 되고, 그 다음 element랑 계속 더해가며 누적된다.
따라서 결과가 45로 누적되어 반환된다.

String Example
partString+element 결과가 partString이 되고, 그 다음 element랑 계속 합쳐지며 누적된다.
따라서 결과가 abcdefgh로 누적되어 합쳐져 반환된다.

Baeldung Stream.reduce() 예제

3. collecting

스트림의 값을 모아주는 기능이다. 다시 컬렉션(Collections)으로 돌려준다.

String[] strs = new String[] {"aa", "bb","cc","bb", "dd", "eee"};
  • Collectors.toList()

List<T> 형태로 결과를 반환한다.

List<String> result = Arrays.stream(strs).collect(Collectors.toList());
// "aa","bb","cc","bb","dd","eee"
  • Collectors.toSet()

Set<T> 형태로 결과를 반환한다.

Set<String> toSetResult = Arrays.stream(strs).collect(Collectors.toSet());
// "aa","bb","cc","dd","eee"
  • Collectors.toMap()

Map<T> 형태로 결과를 반환한다.

Map<String,Integer> toMapResult = Arrays.stream(strs)
			.collect(Collectors.toMap(String::new, String::length));
// aa 2
// bb 2
// cc 2
// dd 2
// eee 3

또, toMap의 기능을 사용해서 List -> Map으로 변환할 수 있다.

class Book {
    private String name;
    private int releaseYear;
    private String isbn;
     
    // getters and setters
}

public Map<String, String> listToMap(List<Book> books) {
    return books.stream()
    		.collect(Collectors.toMap(Book::getIsbn, Book::getName));
}

Baeldung Stream.collecting() toMap 예제

  • Collectors.joining()

스트림의 작업 결과를 하나의 스트링(String)으로 연결한다. (Collection이 아닌 String으로)

세가지 인자를 입력할 수 있다.

delimiter: 각 요소 중간에 들어가는 구분자
prefix: 이어붙인 결과 맨 앞에 붙는 문자
suffix: 이어붙인 결과 맨 끝에 붙는 문자

String result = Arrays.stream(strs).collect(Collectors.joining(","));
// aa,bb,cc,bb,dd
String result = Arrays.stream(strs).collect(Collectors.joining(",","z",""));
// zaa,zbb,zcc,zbb,zdd
String result = Arrays.stream(strs).collect(Collectors.joining(",","","z"));
// aaz,bbz,ccz,bbz,ddz
  • Collectors.collectingAndThen

collect 후, Collectors 결과를 리턴하는데 그 결과를 한번더 감싸서 어떠한 형태로 감싸서 다른 컬렉터를 반환한다.

Collections::unmodifiableList - List가 불변하도록 박제해버린 결과를 반환

Arrays.stream(strs).collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); 

Collection::toString - String 으로 반환

Arrays.stream(strs).collect(Collectors.collectingAndThen(Collectors.toList(), Collection::toString));

Optional::get - Optional get 이니까 리턴 값을 반환

Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Integer::intValue)), Optional::get)
  • Collectors.groupingBy()

특정 조건으로 요소들을 그룹화하여 Map 타입으로 반환한다.
예를 들어, 나이, 이름, 성별을 가진 클래스를 나이 기준으로 그룹화 할 수 있다.

예제에서는 String 길이에 따라 그룹핑하였다.

Map<Integer, List<String>> groupingByResult = Arrays.stream(strs)
           .collect(Collectors.groupingBy(String::length, Collectors.toList()));
       // 1. aa, bb, cc, dd
       // 2. eee
  • Collectors.partitioningBy()

Predicate로 특정 조건을 받아 해당 조건을 만족하면 true, 아니면 false 그룹으로 구분하여 Map 타입으로 반환한다.

Map<Boolean, List<String>> result = Arrays.stream(strs)
  	.collect(partitioningBy(s -> s.length() > 2))
  
  // true: [eee], false: [aa,bb,cc,bb,dd]
  • Collectors.averagingInt()

요소(Integer)들의 평균을 Double형으로 반환한다.

double averagingInt = ages.stream().collect(Collectors.averagingInt(Integer::intValue));
  • Collectors.summingInt()

요소들의 합을 Integer형으로 반환한다.

int summingInt = ages.stream().collect(Collectors.summingInt(Integer::intValue));
  • Collectors.summarizingInt()

다양한 연산 결과를 IntSummaryStatistics 형으로 반환한다.

IntSummaryStatistics intSummaryStatistics = ages.stream().collect(Collectors.summarizingInt(Integer::intValue));
intSummaryStatistics.getSum(); // 총 합
intSummaryStatistics.getMin(); // 최소값
intSummaryStatistics.getMax(); // 최대값
intSummaryStatistics.getCount(); // 갯수
intSummaryStatistics.getAverage(); // 평균값

제공 메소드: getCount(), getSum(), getAverage(), getMin(), getMax()

4. Matching

filter처럼 조건을 작성하여 조건에 맞춰서 boolean을 리턴한다.

  • anyMatch()

하나라도 조건을 만족하는 요소가 있는지 확인한다.

boolean isValidAnyMatch = Arrays.stream(strs).anyMatch(element -> element.contains("aa")); // true
  • allMatch()

모든 조건을 만족하는지 확인한다.

boolean isValidAllMatch = Arrays.stream(strs).allMatch(element -> element.contains("gggg")); // false
  • noneMatch()

모든 조건을 만족하지 않는지 확인한다.

boolean isValidNoneMatch1 = Arrays.stream(strs).noneMatch(element -> element.contains("dd")); // false

boolean isValidNoneMatch2 = Arrays.stream(strs).noneMatch(element -> element.contains("zzzzz")); // true

5. forEach

요소를 순회하면서 실행되는 작업이다.
인자로 넘긴 메소드에 요소를 대입하여 호출한다.
주로 System.out::println 메소드 레퍼런스과 같은 출력 함수를 인자로 넘긴다.
Intermediate Operator에 속하는 peek와 유사하다고 볼 수 있다.

Arrays.asList("String","Integer","Boolean","Long").stream()
	.forEach(System.out::println);

6. 람다

수학에서 사용하는 함수를 보다 단순하게 표현하는 방법이다.
람다 익스프레션Method Reference로도 대체가 가능하다.

람다는 다음과 같은 특징을 가진다.

  1. 람다 대수는 이름을 가질 필요가 없다. (익명 함수)
  2. 두 개 이상의 입력이 있는 함수는 최종적으로 1개의 입력만 받는 람다 대수로 단순화 될 수 있다.

마무리

Stream을 알게 되었으면 아는 것에 멈추지 않고 자주 사용하며 응용하는 능력이 요구된다.

정리해놨으니 자주 사용하고 참고하면서 함수형 프로그래밍에 익숙해지자.

profile
차근차근 develog

0개의 댓글