Java Stream (4) 스트림 주요 메소드

2020. 7. 17. 15:12· Java
목차
  1. 데이터 선별
  2. 데이터 개수 조절
  3. 데이터 변환
  4. 단일 스트림으로 변환
  5. 데이터 일치 여부
  6. 데이터 검색
  7. 데이터 연산
  8. for loop 활용
  9. Stream 활용
  10. 최종 연산

스트림 주요 메소드

이번 포스팅에서는 스트림 활용 시 주로 사용되는 메소드를 다룬다.

지난 포스팅과 비슷한 예제로 진행하였다.

@Getter
@AllArgsConstructor
@EqualsAndHashCode    // equals, hashcode 자동 생성
public class City {
    private String name;
    private double area;
    private int population;
    private String areaCode;
}

List<City> cities = Arrays.asList(
        new City("Seoul", 605.2, 9720846, "02"),
        new City("Incheon", 1063.3, 2947217, "032"),
        new City("Ulsan", 1062, 1142190, "052"),
        new City("Daegu", 883.5, 2427954, "053"),
        new City("Gwangju", 501.1, 1455048, "062"),
        new City("Busan", 770.1, 3404423, "051")
);

데이터 선별

조건에 따라 데이터를 선별하는 중간 연산이다.

cities.add(new City("Seoul", 0, 0, "02"));

List<City> streamNameList = cities.stream()
        .filter(city -> city.getArea() > 800) // ← 데이터 필터링
        .distinct()                           // ← 중복 제거
        .collect(Collectors.toList());

Stream::filter 는 Predicate 를 인자로 받는다.

중복 요소를 제거하고 싶다면 Stream::distinct 를 사용한다. (Seoul 하나가 삭제된다)

중복을 판단하는 조건은 City 클래스에 오버라이드한 equals / hashcode이다.

데이터 개수 조절

스트림 데이터를 자르거나 특정 요소만 선택한다.

  1. 개수 제한

    • Stream::limit 로 개수를 조절할 수 있다.

        List<String> streamNameList = cities.stream()
                .filter(city -> city.getArea() > 800)
                .sorted(Comparator.comparing(City::getArea))
                .map(City::getName)
                .limit(2) // ← 최대 두 개의 원소만 반환한다.
                .collect(Collectors.toList());
  2. 건너뛰기

    • Stream::skip 으로 처음 n개를 건너뛸 수 있다.

        cities.stream()
                .filter(city -> city.getArea() > 800)
                .sorted(Comparator.comparing(City::getArea))
                .map(City::getName)
                .skip(1) // ← 첫 번째 원소는 무시한다.
                .forEach(System.out::println);
      
        // 결과 (첫 번째 데이터 Daegu는 스킵)
        Ulsan
        Incheon
  3. 특정 조건에 부합하는 데이터만 선택

    • 전제조건

      • Java 9 이상에서 지원된다.

      • 데이터는 반드시 사용할 기준을 조건으로 정렬되어 있는 상태여야 한다.

          // 예시 데이터를 area 오름차순으로 재가공
          List<City> orderByAreaList = cities.stream()
                  .sorted(Comparator.comparing(City::getArea))
                  .collect(Collectors.toList());
    1. Stream::takeWhile

      • 스트림을 순회하다 조건이 false가 되는 순간 남은 데이터를 버린다.

          orderByAreaList.stream()
                  .takeWhile(city -> city.getArea() > 700)
                  .map(City::getName)
                  .forEach(System.out::println);
        
          // 결과
          Gwangju
          Seoul
        • 데이터는 광주, 서울, 부산, 대구, 인천 순으로 정렬되어 있으나, 조건에 부합하지 않는 데이터인 부산부터 모든 데이터를 제거한다.
    2. Stream::dropWhile

      • 스트림을 순회하다 조건이 true가 되는 순간 지금까지 순회한 데이터를 모두 버린다.

          orderByAreaList.stream()
                  .dropWhile(city -> city.getArea() < 700)
                  .map(City::getName)
                  .forEach(System.out::println);
        
          // 결과
          Busan
          Daegu
          Ulsan
          Incheon
    3. Stream::filter 와의 차이점

      • Stream::filter 는 모든 원소를 검사하여 조건에 맞는 데이터만 선택하나, takeWhile, dropWhile은 데이터를 버리는 방식이다.

데이터 변환

City 는 도시명, 면적, 인구수, 지역번호 데이터를 갖는 클래스이다.

사용자의 목적에 따라 도시명 데이터만 사용해야 할 수도 있을 것이다.

Stream::map 은 스트림 원소를 변환한다.

List<String> filteringList = cities.stream()
        .filter(city -> city.getArea() > 800)
        .sorted(Comparator.comparing(City::getArea))
        .map(city -> city.getName()) // City 클래스에서 도시명만 사용하도록 한다.
        .collect(Collectors.toList());

// 결과 (첫 번째 데이터 Daegu는 스킵)
Ulsan
Incheon

단일 스트림으로 변환

만약 cities에서 지역 번호에 사용된 숫자에서 중복을 제거한 목록를 스트림으로 얻어내고 싶다면 어떻게 해야 할까?

먼저 areaCode의 목록을 추출해본다.

List<String> areaCodes = cities.stream()
        .map(City::getAreaCode)
        .collect(Collectors.toList());
// ["02", "032", "052", "053", "062", "051"]
  1. areaCodes를 문자 단위로 분해

     0, 2
     0, 3, 2
     0, 5, 2
     0, 5, 3
     0, 6, 2
     0, 5, 1
  2. areaCodes를 단일 스트림으로 변환

     0, 2, 0, 3, 2, 0, 5, 2, 0, 5, 3, 0, 6, 2, 0, 5, 1

areaCodes를 스트림으로 추출하면 위의 1번처럼 문자열 하나가 데이터 하나로 변환된다.

문자 단위로 분해하여 2와 같은 단일 문자열 스트림을 반환해야 한다.

이럴 때 사용하는 것이 Stream::flatMap 이다.

areaCodes.stream()
        .map(areaCode -> areaCode.split("")) // areaCode를 문자 단위로 분해한 배열로 변환
        .flatMap(Arrays::stream)             // 변환된 스트림을 단일 스트림으로 변환
        .distinct()                          // 중복 제거
        .forEach(System.out::println);

// 결과
0
2
3
5
6
1

데이터 일치 여부

여기서 소개하는 메소드는 모두 boolean을 반환하는 최종 연산이다.

Short circuit evaluation 기법이 적용되어 값을 즉시 반환할 수 있는 상태가 되면 남은 원소를 확인하지 않는다.

  • Short circuit 예시

      if (true || false) { ... }
      if (false && true) { ... }
    • 두 식 모두 첫 번째 boolean만 평가하면 결과를 도출할 수 있다.
    • 따라서 두 번째 boolean은 확인하지 않는다.
  1. Stream::anyMatch

    • 검색 조건에 맞는 원소가 1개 이상인 경우 true

      boolean areaOver1000 = cities.stream()
            .anyMatch(city -> city.getArea() > 1000);
      
      boolean areaOver2000 = cities.stream()
            .anyMatch(city -> city.getArea() > 2000);
      
      System.out.println(areaOver1000);
      System.out.println(areaOver2000);
      
      // 결과
      true
      false
  2. Stream::allMatch

    • 모든 원소가 검색 조건에 일치해야 true

      boolean allAreasOver100 = cities.stream()
            .allMatch(city -> city.getArea() > 100);
      
      boolean allAreasOver1000 = cities.stream()
            .allMatch(city -> city.getArea() > 1000);
      
      System.out.println(allAreasOver100);
      System.out.println(allAreasOver1000);
      
      // 결과
      true
      false
  3. Stream::nonMatch

    • 모든 원소가 검색 조건에 일치하지 않아야 true (allMatch와 반대 동작)

      boolean allAreasAreNotOver1000 = cities.stream()
            .noneMatch(city -> city.getArea() > 1000);
      
      boolean allAreasAreNotOver2000 = cities.stream()
            .noneMatch(city -> city.getArea() > 2000);
      
      System.out.println(allAreasAreNotOver1000);
      System.out.println(allAreasAreNotOver2000);
      
      // 결과
      false
      true

데이터 검색

검색 메소드를 사용하면 스트림 파이프라인에서 적절한 데이터를 찾는다.

또한 데이터를 찾는 순간 검색이 종료되는 Short-circuit 기법이 적용된다.

검색 조건에 부합하는 데이터가 없을 수도 있다. 따라서 반환 시엔 Optional 을 사용한다.

  1. Stream::findAny

    • 검색 조건에 부합하는 임의의 데이터를 반환한다.

    • 순서에 구애받지 않기 때문에 병렬 스트림을 생성하면 결과가 다르게 나올 수 있다.

      Optional<City> find = cities.stream()
            .filter(city -> city.getArea() > 500) // 검색 조건은 filter를 활용한다.
            .findAny();
      
      System.out.println(find.get().getName());
  2. Stream::findFirst

    • 검색 조건에 부합하는 첫 번째 데이터를 반환한다.

      Optional<City> find = cities.stream()
            .filter(city -> city.getArea() > 500)
            .findFirst();
      
      System.out.println(find.get().getName());

데이터 연산

지금까지는 스트림 원소에 독립적인 로직을 적용하였다.

임의의 숫자가 담긴 배열의 총 합을 스트림을 활용하여 구해보자.

  • 데이터

      Random random = new Random();
    
      int[] array = new int[100];
      for (int i = 0; i < 100; i++) {
          array[i] = random.nextInt(100);
      }

for loop 활용

int sum = 0;
for (int num : array) {
    sum += num;
}
System.out.println(sum);

Stream 활용

Stream::reduce 메소드는 값을 연쇄적으로 계산할 때 사용한다.

reduce는 (int), IntBinaryOperator 를 매개변수로 받는다.

첫 번째 int는 초기값으로, 생략이 가능하다.

IntBinaryOperator 는 두 개의 int형 매개변수를 받아 int를 반환하는 함수형 인터페이스이다.

int sum = Arrays.stream(array)
        .reduce(0, Integer::sum);

System.out.println(sum);

최종 연산

최종 연산은 자료형, 컬렉션, void를 반환한다.

  1. Collection 반환

    1. List

      • 예제에서도 계속 사용했던 형태이다.

        List<String> streamNameList = cities.stream()
              .filter(city -> city.getArea() > 800)
              .sorted(Comparator.comparing(City::getArea))
              .map(City::getName)
              .collect(Collectors.toList());
    2. Map

      • 공통 요소를 그룹핑한다는 개념으로 Map을 생성한다.

        Map<String, List<City>> cityMap = cities.stream()
              .filter(city -> city.getArea() > 800)
              .collect(Collectors.groupingBy(City::getName));
    3. Set

      • 원본 List 에서 조건에 맞는 데이터만 선별하여 Set 으로 다시 저장한다.

        Set<City> citySet = cities.stream()
              .filter(city -> city.getArea() > 800)
              .collect(Collectors.toSet());
  2. 자료형 반환

    • 스트림의 원소 개수를 세는 예제

        System.out.println(cities.stream().count());
      
        // 결과
        6
  3. void

    • 순회

        cities.stream().map(City::getName).forEach(System.out::println);
      
        // 결과
        Seoul
        Incheon
        Ulsan
        Daegu
        Gwangju
        Busan
저작자표시 비영리 동일조건 (새창열림)

'Java' 카테고리의 다른 글

Java Stream (6) 스트림 생성  (0) 2020.07.22
Java Stream (5) 기본 자료형 스트림  (0) 2020.07.22
Java Stream (3) 스트림의 개념  (0) 2020.07.15
Java Stream (2) 람다 표현식  (0) 2020.07.08
Java Stream (1) 스트림 개요  (0) 2020.06.02
  1. 데이터 선별
  2. 데이터 개수 조절
  3. 데이터 변환
  4. 단일 스트림으로 변환
  5. 데이터 일치 여부
  6. 데이터 검색
  7. 데이터 연산
  8. for loop 활용
  9. Stream 활용
  10. 최종 연산
'Java' 카테고리의 다른 글
  • Java Stream (6) 스트림 생성
  • Java Stream (5) 기본 자료형 스트림
  • Java Stream (3) 스트림의 개념
  • Java Stream (2) 람다 표현식
감동이중요해
감동이중요해
https://github.com/dhmin5693 dhmin5693@naver.com
감동이중요해
티끌모아 산을 쌓아보자
감동이중요해
전체
오늘
어제
  • 분류 전체보기 (111)
    • 알고리즘 (35)
    • Infra & Dev tools (10)
      • Git (2)
      • Cloud platform (5)
      • Mac, Linux (3)
    • BigData (1)
    • IT 도서 (11)
      • Clean Code (8)
    • Java (36)
      • Spring framework (19)
      • JPA (5)
      • Domain Driven Design (3)
    • Database (2)
      • oracle (1)
      • mysql (0)
    • Computer Science (7)
      • 운영체제 (7)
    • 기타 (9)
      • 크롤링(파이썬) (1)
      • 회고 (4)
      • Career (0)

블로그 메뉴

  • 홈
  • 태그
  • 미디어로그
  • 위치로그
  • 방명록

공지사항

  • About me

인기 글

태그

  • 우아한테크캠프2기
  • 메모리
  • Clean Code
  • Stream
  • 회고
  • Java
  • Spring
  • JPA
  • 운영체제
  • 영속상태
  • AWS
  • Linux
  • 프로세스
  • 알고리즘
  • Database
  • bean
  • 블라인드공채
  • DDD
  • Mac
  • 영속

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
감동이중요해
Java Stream (4) 스트림 주요 메소드
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.