반응형
1. 복잡한 정렬 시나리오 활용
1.1 주문 데이터 다중 조건 정렬 예제
public class Order {
private LocalDateTime orderDate;
private String customerId;
private BigDecimal amount;
private OrderStatus status;
// 생성자, getter, setter 생략
}
// 실제 활용 예시
public class OrderProcessor {
public List<Order> getProcessedOrders(List<Order> orders) {
return orders.stream()
.sorted(
// 1. 주문 상태 우선순위: PENDING -> PROCESSING -> COMPLETED -> CANCELLED
Comparator.comparing(Order::getStatus,
Comparator.comparingInt(status -> {
switch (status) {
case PENDING: return 1;
case PROCESSING: return 2;
case COMPLETED: return 3;
case CANCELLED: return 4;
default: return 5;
}
}))
// 2. 주문 금액 내림차순
.thenComparing(Order::getAmount, Comparator.reverseOrder())
// 3. 주문 일자 오름차순
.thenComparing(Order::getOrderDate)
)
.collect(Collectors.toList());
}
}
이 예제에서는:
- 주문 상태별로 우선순위를 부여하여 정렬
- 같은 상태 내에서는 주문 금액이 큰 순서대로 정렬
- 금액도 같다면 주문 일자 순으로 정렬
- Comparator.comparing()과 thenComparing()을 체이닝하여 복잡한 정렬 조건을 구현
1.2 사용자 정의 정렬 예제
public class Employee {
private String name;
private String department;
private String position;
private BigDecimal salary;
private LocalDate joinDate;
// 생성자, getter, setter 생략
}
public class HRSystem {
// 직급별 가중치 정의
private static Map<String, Integer> positionWeights = new HashMap<>() {{
put("사원", 1);
put("대리", 2);
put("과장", 3);
put("차장", 4);
put("부장", 5);
}};
public List<Employee> getSortedEmployees(List<Employee> employees) {
return employees.stream()
.sorted(
// 1. 부서별 그룹핑
Comparator.comparing(Employee::getDepartment)
// 2. 직급 가중치 기준 정렬
.thenComparing(emp -> positionWeights.getOrDefault(emp.getPosition(), 0))
// 3. 연봉 내림차순
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
// 4. 입사일 기준
.thenComparing(Employee::getJoinDate)
)
.collect(Collectors.toList());
}
// 부서별 급여 통계 계산
public Map<String, DepartmentStats> calculateDepartmentStats(List<Employee> employees) {
return employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
empList -> {
DoubleSummaryStatistics salaryStats = empList.stream()
.mapToDouble(e -> e.getSalary().doubleValue())
.summaryStatistics();
return new DepartmentStats(
empList.size(), // 부서 인원
BigDecimal.valueOf(salaryStats.getAverage()),// 평균 급여
BigDecimal.valueOf(salaryStats.getMin()), // 최저 급여
BigDecimal.valueOf(salaryStats.getMax()), // 최고 급여
calculateMedianSalary(empList) // 중간값 급여
);
}
)
));
}
// 중간값 급여 계산
private BigDecimal calculateMedianSalary(List<Employee> employees) {
List<BigDecimal> sortedSalaries = employees.stream()
.map(Employee::getSalary)
.sorted()
.collect(Collectors.toList());
int size = sortedSalaries.size();
if (size == 0) return BigDecimal.ZERO;
if (size % 2 == 0) {
return sortedSalaries.get(size/2 - 1)
.add(sortedSalaries.get(size/2))
.divide(new BigDecimal("2"), 2, RoundingMode.HALF_UP);
} else {
return sortedSalaries.get(size/2);
}
}
}
2. 고급 데이터 변환 예제
2.1 주문 데이터 분석 시스템
public class OrderAnalyzer {
public class OrderSummary {
private LocalDate date;
private Map<String, Integer> productCounts;
private BigDecimal totalAmount;
private Set<String> uniqueCustomers;
}
public List<OrderSummary> analyzeOrders(List<Order> orders) {
return orders.stream()
// 날짜별로 그룹화
.collect(Collectors.groupingBy(
order -> order.getOrderDate().toLocalDate(),
Collectors.collectingAndThen(
Collectors.toList(),
dailyOrders -> new OrderSummary(
dailyOrders.get(0).getOrderDate().toLocalDate(),
// 상품별 주문 수량 집계
dailyOrders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getProductId,
Collectors.summingInt(OrderItem::getQuantity)
)),
// 총 주문 금액 계산
dailyOrders.stream()
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add),
// 유니크 고객 수 계산
dailyOrders.stream()
.map(Order::getCustomerId)
.collect(Collectors.toSet())
)
)
))
.values()
.stream()
.sorted(Comparator.comparing(OrderSummary::getDate))
.collect(Collectors.toList());
}
}
2.2 데이터 변환과 집계의 고급 활용
public class SalesAnalyzer {
// 지역별, 상품 카테고리별 매출 분석
public Map<String, Map<String, SalesStatistics>> analyzeSalesByRegionAndCategory(
List<Sale> sales) {
return sales.stream()
.collect(Collectors.groupingBy(
Sale::getRegion,
Collectors.groupingBy(
Sale::getCategory,
Collectors.collectingAndThen(
Collectors.toList(),
categorySales -> new SalesStatistics(
// 총 매출
categorySales.stream()
.map(Sale::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add),
// 평균 구매 금액
categorySales.stream()
.map(Sale::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(categorySales.size()),
2, RoundingMode.HALF_UP),
// 최대 구매 금액
categorySales.stream()
.map(Sale::getAmount)
.max(Comparator.naturalOrder())
.orElse(BigDecimal.ZERO),
// 구매 건수
categorySales.size(),
// 유니크 고객 수
categorySales.stream()
.map(Sale::getCustomerId)
.distinct()
.count()
)
)
)
));
}
// 시계열 매출 분석
public List<TimeSeriesData> analyzeTimeSeriesSales(List<Sale> sales,
Period aggregationPeriod) {
return sales.stream()
// 기간별로 그룹화
.collect(Collectors.groupingBy(
sale -> getAggregationKey(sale.getDateTime(), aggregationPeriod),
Collectors.collectingAndThen(
Collectors.toList(),
periodSales -> new TimeSeriesData(
// 기간 시작일시
getStartOfPeriod(periodSales.get(0).getDateTime(),
aggregationPeriod),
// 기간 매출 합계
periodSales.stream()
.map(Sale::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add),
// 기간 내 평균 주문 금액
calculateAverageAmount(periodSales),
// 전기 대비 증감률
calculateGrowthRate(periodSales)
)
)
))
.values()
.stream()
.sorted(Comparator.comparing(TimeSeriesData::getDateTime))
.collect(Collectors.toList());
}
}
이러한 고급 예제들은 실제 업무에서 자주 마주치는 복잡한 데이터 처리 요구사항을 Stream API를 사용하여 효과적으로 해결하는 방법을 보여줍니다.
주요 포인트:
- 복합 정렬 조건의 구현: 여러 조건을 조합하여 데이터를 정렬하는 방법
- 중첩된 데이터 처리: 그룹화와 집계를 조합하여 복잡한 통계 생성
- 사용자 정의 집계: 단순 합계나 평균을 넘어선 복잡한 통계 계산
- 시계열 데이터 처리: 시간 기반 데이터의 효과적인 처리 방법
이러한 예제들은 Stream API의 강력한 기능을 실제 비즈니스 로직에 적용하는 방법을 보여주며, 코드의 가독성과 유지보수성을 향상시킬 수 있습니다.
반응형