스트림 주요 메소드
이번 포스팅에서는 스트림 활용 시 주로 사용되는 메소드를 다룬다.
지난 포스팅과 비슷한 예제로 진행하였다.
@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이다.
데이터 개수 조절
스트림 데이터를 자르거나 특정 요소만 선택한다.
개수 제한
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());
건너뛰기
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
특정 조건에 부합하는 데이터만 선택
전제조건
Java 9 이상에서 지원된다.
데이터는 반드시 사용할 기준을 조건으로 정렬되어 있는 상태여야 한다.
// 예시 데이터를 area 오름차순으로 재가공 List<City> orderByAreaList = cities.stream() .sorted(Comparator.comparing(City::getArea)) .collect(Collectors.toList());
Stream::takeWhile
스트림을 순회하다 조건이 false가 되는 순간 남은 데이터를 버린다.
orderByAreaList.stream() .takeWhile(city -> city.getArea() > 700) .map(City::getName) .forEach(System.out::println); // 결과 Gwangju Seoul
- 데이터는 광주, 서울, 부산, 대구, 인천 순으로 정렬되어 있으나, 조건에 부합하지 않는 데이터인 부산부터 모든 데이터를 제거한다.
Stream::dropWhile
스트림을 순회하다 조건이 true가 되는 순간 지금까지 순회한 데이터를 모두 버린다.
orderByAreaList.stream() .dropWhile(city -> city.getArea() < 700) .map(City::getName) .forEach(System.out::println); // 결과 Busan Daegu Ulsan Incheon
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"]
areaCodes를 문자 단위로 분해
0, 2 0, 3, 2 0, 5, 2 0, 5, 3 0, 6, 2 0, 5, 1
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은 확인하지 않는다.
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
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
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
을 사용한다.
Stream::findAny
검색 조건에 부합하는 임의의 데이터를 반환한다.
순서에 구애받지 않기 때문에 병렬 스트림을 생성하면 결과가 다르게 나올 수 있다.
Optional<City> find = cities.stream() .filter(city -> city.getArea() > 500) // 검색 조건은 filter를 활용한다. .findAny(); System.out.println(find.get().getName());
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를 반환한다.
Collection 반환
List
예제에서도 계속 사용했던 형태이다.
List<String> streamNameList = cities.stream() .filter(city -> city.getArea() > 800) .sorted(Comparator.comparing(City::getArea)) .map(City::getName) .collect(Collectors.toList());
Map
공통 요소를 그룹핑한다는 개념으로
Map
을 생성한다.Map<String, List<City>> cityMap = cities.stream() .filter(city -> city.getArea() > 800) .collect(Collectors.groupingBy(City::getName));
Set
원본
List
에서 조건에 맞는 데이터만 선별하여Set
으로 다시 저장한다.Set<City> citySet = cities.stream() .filter(city -> city.getArea() > 800) .collect(Collectors.toSet());
자료형 반환
스트림의 원소 개수를 세는 예제
System.out.println(cities.stream().count()); // 결과 6
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 |