6장 스트림으로 데이터 수집(처리) 정리
스트림은 중간 연산과 최종 연산으로 구성되어있다.
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
위 코드는 중간연산 filter 와 최종 연산 collect 으로 이루어져 있다.
collect()에 toList() 를 적용하여 리스트 컬렉션으로 만드는데 방법에는 익숙하다.
이번 장에서는 추가적으로 다른 사용 가능한 방식에 대해 공부해본다.
목차
그룹화
분할
리듀싱
1. 그룹화
groupingBy 메서드를 이용하면 데이터를 하나 이상의 특성으로 분류할 수 있다.
이번 포스팅에서는 예시를 위해 다음과 같은 데이터를 이용한다는 점을 참고하자.
예시 데이터
public class Vehicle {
private String name;
private int wheelCount;
private Power power;
enum Power {
다리, 기름, 전기
}
public Vehicle(final String name, final int wheelCount, final Power power) {
this.name = name;
this.wheelCount = wheelCount;
this.power = power;
}
public Power getPower() {
return power;
}
public String getName() {
return name;
}
public int getWheelCount() {
return wheelCount;
}
@Override
public String toString() {
return name;
}
}
public class Main {
public static void main(String[] args) {
List<Vehicle> vehicles = Arrays.asList(
new Vehicle("두발자전거", 2, Vehicle.Power.다리),
new Vehicle("세발사전거", 3, Vehicle.Power.다리),
new Vehicle("오토바이", 2, Vehicle.Power.전기),
new Vehicle("자동차", 4, Vehicle.Power.기름),
new Vehicle("전기자동차", 4, Vehicle.Power.전기),
new Vehicle("지하철", 10, Vehicle.Power.전기)
);
}
}
1.1 타입에 따른 분류
Vehicle 을 동력(Power) 에 따라 분류하기
public class Main {
public static void main(String[] args) {
Map<Vehicle.Power, List<Vehicle>> group =
vehicles.stream().collect(Collectors.groupingBy(Vehicle::getPower));
System.out.println(group);
}
}
//출력결과
//{다리=[두발자전거, 세발사전거], 전기=[오토바이, 전기자동차, 지하철], 기름=[자동차]}
1.2 조건에 따른 분류
Vehicle 의 바퀴 개수가 4개 이상인 것들을 타입에 따라 분류한다고 하자.
먼저 우리가 익히 알고 있는 방법으로는 스트링에서 필터를 이용 후 groupingBy로 묶어주는 방법이 있다.
public class Main {
public static void main(String[] args) {
Map<Vehicle.Power, List<Vehicle>> group = vehicles.stream()
.filter(vehicle -> vehicle.getWheelCount() >= 4)
.collect(Collectors.groupingBy(Vehicle::getPower));
System.out.println(group);
}
}
//출력결과
//{기름=[자동차], 전기=[전기자동차, 지하철]}
그런데 위의 출력 결과를 가만히 보니 문제가 있다.
동력이 '다리'인 부분들이 groupingBy 되기 전에 모두 필터링되어 버려서 출력 결과에 '다리' 에 관한 카테고리 자체가 나오지 않는다.
이러한 문제는 filter의 프리티케이터를 groupingBy 안으로 이동시키면 해결할 수 있다.
public class Main {
public static void main(String[] args) {
Map<Vehicle.Power, List<Vehicle>> group = vehicles.stream()
.collect(Collectors.groupingBy(Vehicle::getPower,
Collectors.filtering(vehicle -> vehicle.getWheelCount() >= 4,
Collectors.toList())));
System.out.println(group);
}
}
//출력결과
//{기름=[자동차], 다리=[], 전기=[전기자동차, 지하철]}
1.3 여러개의 조건
여러 조건으로 그룹핑
public class Main {
Map<Vehicle.Power, Map<Object, List<Vehicle>>> group = vehicles.stream()
.collect(Collectors.groupingBy(Vehicle::getPower,
Collectors.groupingBy(vehicle -> {
if (vehicle.getWheelCount() > 3) {
return "3개초과";
} else {
return "3개이하";
}
})));
System.out.println(group);
}
}
//출력결과
//{기름={3개초과=[자동차]}, 다리={3개이하=[두발자전거, 세발사전거]}, 전기={3개초과=[전기자동차, 지하철], 3개이하=[오토바이]}}
1.4 그외
그 외의 기능으로는 그룹핑된 항목들을 가지고 연산을 할 수 있다.
- counting
public static void main(String[] args) {
Map<Vehicle.Power, Long> group = vehicles.stream()
.collect(Collectors.groupingBy(Vehicle::getPower, Collectors.counting()));
System.out.println(group);
}
}
//출력결과
//{기름=1, 다리=2, 전기=3}
- maxBy
public static void main(String[] args) {
Map<Vehicle.Power, Optional<Vehicle>> group = vehicles.stream()
.collect(Collectors.groupingBy(Vehicle::getPower,
Collectors.maxBy(Comparator.comparingInt(Vehicle::getWheelCount))));
System.out.println(group);
}
}
//출력결과
//{전기=Optional[지하철], 기름=Optional[자동차], 다리=Optional[세발사전거]}
동력으로 분류한 후 각 동력별로 바퀴 개수가 가장 많은 객체를 반환하고 있다.
여기서 주목해야 할 부분은 반환형이 Optional 이라는 점인데 Optional 을 제거하는 방법으로는 collectingAndThen 을 사용하는 방법이 있다.
...
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
public class Main {
public static void main(String[] args) {
Map<Vehicle.Power,Vehicle> group = vehicles.stream()
.collect(groupingBy(Vehicle::getPower,
collectingAndThen(maxBy(comparingInt(Vehicle::getWheelCount)),
Optional::get)));
System.out.println(group);
}
}
//출력결과
//{다리=세발사전거, 전기=지하철, 기름=자동차}
(Collectors 를 static 으로 import 해준후 생략한 모습)
2. 분할
분할은 프리디케이트로 true 와 false 로 분류하는 기능이다.
public class Main {
public static void main(String[] args) {
Map<Boolean, List<Vehicle>> group = vehicles.stream()
.collect(Collectors.partitioningBy(
vehicle -> vehicle.getPower() == Vehicle.Power.다리));
System.out.println(group);
}
}
//출력결과
//{false=[오토바이, 자동차, 전기자동차, 지하철], true=[두발자전거, 세발사전거]}
참 값을 가져오고 싶다면 group.get(true) 를 통해 가져올 수 있다.
우리가 많이 사용해왔던 스트림에서 필터링 후 리스트에 결과를 수집하는 방법과 비교했을 때, 분할은 false 값까지 정보를 저장한다는 장점이 있다.
예를 들어 동력이 다리인 것 중 바퀴 수가 가장 많은 것과 다리가 아닌 것 중 바퀴 수가 가장 많은 것을 확인하고 싶다면 다음과 같이 할 수 있다.
...
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
public class Main {
public static void main(String[] args) {
Map<Boolean, Vehicle> group = vehicles.stream()
.collect(partitioningBy(vehicle -> vehicle.getPower()
== Vehicle.Power.다리,
collectingAndThen(maxBy(comparingInt(Vehicle::getWheelCount)),
Optional::get)));
System.out.println(group);
}
}
//출력결과
//{false=지하철, true=세발사전거}
3. 리듀싱
3.1 최댓값, 최솟값 검색
collect 함수의 인자로 maxBy 혹은 minBy 를 사용하여 최댓값 혹은 최솟값을 구할 수 있다.
이때 maxBy 와 minBy의 인자로는 Compatator 를 입력한다.
public class Main {
public static void main(String[] args) {
List<String> numbers = Arrays.asList("1","2","3","4","5");
Comparator<String> stringComparator =
Comparator.comparingInt(Integer::parseInt);
Optional<String> maxNumber = numbers.stream()
.collect(Collectors.maxBy(stringComparator));
System.out.println(maxNumber);
}
}
//출력결과
//Optional[5]
3.2 문자열 연결
joining 메서드를 사용하여 하나의 문자열로 반환할 수 있다.
public class Main {
public static void main(String[] args) {
List<String> numbers = Arrays.asList("1","2","3","4","5");
Comparator<String> stringComparator =
Comparator.comparingInt(Integer::parseInt);
String numberString = numbers.stream()
.collect(Collectors.joining());
System.out.println(numberString);
}
}
//출력결과
//12345
3.3 reducing 메서드
reducing 메서드를 사용하여 사용자가 하고 싶은 동작을 직접 작성할 수 있다.
public class Main {
public static void main(String[] args) {
List<String> numbers = Arrays.asList("1", "2", "3", "4", "5");
int sum = numbers.stream()
.collect(Collectors.reducing(0, Integer::parseInt, (x, y) -> x + y));
System.out.println(sum);
}
}
//출력결과
//15
reducing 메서드는 3개의 인자를 받고 있다.
첫 번째 인자는 리듀싱 연산의 시작 값이거나 스트림에 인수가 없을 때의 반환 값이다.
두 번째 인자는 변환 함수다.
세 번째 인자는 두 항목을 하나의 값으로 더하는 Operator 이다.
'책 📖' 카테고리의 다른 글
'단위 테스트 - 생산성과 품질을 위한 단위 테스트 원칙과 패턴'을 읽고 (0) | 2022.03.13 |
---|---|
존 도어의 OKR 핵심 내용 정리 (0) | 2021.01.19 |
클린 코드, 클린 코드 40 가지 원칙! (0) | 2020.11.25 |
함께 자라기, 애자일로 가는길 (0) | 2020.08.03 |
소프트웨어 장인을 읽고 (1) | 2020.07.21 |