1. Stream.ofNullable() 을 사용한 안전한 코드
코드
/**
* Stream.ofNullable()
*/
log.info("########## Stream.ofNullable() ##########");
// Optional 사용하는 기존 방식
List<String> emails = Arrays.asList("user@gmail.com", null, "admin@gmail.com", null);
List<String> filteredEmails = emails.stream()
.map(Optional::ofNullable)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
log.info("Null 제거: {}", filteredEmails);
// Java 9의 Stream::ofNullable을 사용한 더 간단한 방식
List<String> filteredEmailsNewWay = emails.stream()
.flatMap(Stream::ofNullable) // 👋 Bye-bye, nulls!
.collect(Collectors.toList());
log.info("new Null 제거: {}", filteredEmailsNewWay);
결과

- Java 9 이상에서만 사용 가능합니다.
- null 값이 자주 발생하는 경우에는 성능상의 오버헤드가 있을 수 있습니다.
2. Collectors.collectingAndThen() 으로 한번에 처리
코드
/**
* Collectors.collectingAndThen()
*/
log.info("########## Collectors.collectingAndThen() ##########");
@Data
@AllArgsConstructor
class Product {
private String name;
private int price;
}
List<Product> products = Arrays.asList(
new Product("USB 메모리", 15000),
new Product("마우스", 28000),
new Product("키보드", 45000),
new Product("모니터", 250000),
new Product("노트북", 1200000),
new Product("스마트폰", 890000)
);
// 이전 방식: 두 단계로 분리하여 처리
double averagePrice = products.stream()
.collect(Collectors.averagingDouble(Product::getPrice)); // 먼저 평균 계산
long roundedPrice = Math.round(averagePrice); // 따로 반올림 처리
log.info("평균 반올림: {}", roundedPrice);
// collectingAndThen 사용: 한 번에 처리
long averagePriceNewWay = products.stream()
.collect(Collectors.collectingAndThen(
Collectors.averagingDouble(Product::getPrice),
Math::round // 평균 계산과 반올림을 한 번에 처리
));
log.info("new 평균 반올림: {}", averagePriceNewWay);
결과

- 중간 변수(평균값)를 사용할 필요가 없습니다.
- 하나의 연산으로 깔끔하게 처리할 수 있어 가독성이 좋습니다.
3. takeWhile() & dropWhile() 로 정렬데이터 처리
코드
/**
* takeWhile() & dropWhile()
*/
log.info("########## takeWhile() & dropWhile() ##########");
@Data
@AllArgsConstructor
class Student {
private String name;
private int score;
}
// Java 9 이전 방식
List<Product> budgetProducts = products.stream()
.filter(p -> p.getPrice() < 50000)
.collect(Collectors.toList());
log.info("가성비 제품: {}", budgetProducts);
// Java 9의 takeWhile 사용 (가격 순으로 정렬되어 있다고 가정)
List<Product> budgetProductsNewWay = products.stream()
.sorted(Comparator.comparing(Product::getPrice))
.takeWhile(p -> p.getPrice() < 50000) // 5만원 미만 제품들만 취함
.collect(Collectors.toList());
log.info("new 가성비 제품: {}", budgetProductsNewWay);
// Java 9의 dropWhile 사용
List<Product> premiumProductsNewWay = products.stream()
.sorted(Comparator.comparing(Product::getPrice))
.dropWhile(p -> p.getPrice() < 50000) // 5만원 이상 제품들만 남김
.collect(Collectors.toList());
log.info("new 프리미엄 제품: {}", premiumProductsNewWay);
결과

- 반드시 데이터가 정렬되어 있어야 예상된 결과를 얻을 수 있습니다.
- 조건이 한번 false가 되면 이후 요소는 더 이상 검사하지 않습니다.
4. Stream.peek() 로 디버깅
코드
/**
* Stream.peek()
*/
log.info("########## Stream.peek() ##########");
// peek() 사용 이전 방식 - 디버깅이나 로깅을 위해 중간 변수 사용
List<Product> filteredProducts = products.stream()
.filter(p -> p.getPrice() > 100000)
.collect(Collectors.toList());
log.info("필터링된 제품: {}", filteredProducts);
List<Product> discountedProducts = filteredProducts.stream()
.map(p -> new Product(p.getName(), (int)(p.getPrice() * 0.9)))
.collect(Collectors.toList());
log.info("할인된 제품: {}", discountedProducts);
// peek() 사용 방식 - 체이닝을 끊지 않고 중간 처리 과정 확인
List<Product> result = products.stream()
.filter(p -> p.getPrice() > 100000)
.peek(p -> log.info("new 필터링된 제품: {}, 가격: {}원", p.getName(), p.getPrice()))
.map(p -> new Product(p.getName(), (int)(p.getPrice() * 0.9)))
.peek(p -> log.info("new 할인된 가격: {}, 가격: {}원", p.getName(), p.getPrice()))
.collect(Collectors.toList());
결과

- 스트림 처리를 중단하지 않고 디버깅이 가능합니다.
- 반드시 디버깅 목적으로만 사용해야 합니다.(비지니스 로직에 사용하지 말 것)
- 스트림 요소를 중간에 수정하는 목적으로 사용해서는 안됩니다.
5. Collectors.teeing() 으로 두가지 연산을 동시에
코드
/**
* Collectors.teeing()
*/
log.info("########## Collectors.teeing() ##########");
List<Student> students = Arrays.asList(
new Student("김철수", 95),
new Student("이영희", 88),
new Student("박민수", 73),
new Student("정지원", 98),
new Student("최동현", 65)
);
// Java 12 이전 방식
Optional<Student> maxScoreStudent = students.stream()
.max(Comparator.comparing(Student::getScore));
Optional<Student> minScoreStudent = students.stream()
.min(Comparator.comparing(Student::getScore));
Map<String, Optional<Student>> resultOldWay = new HashMap<>();
resultOldWay.put("최저점", minScoreStudent);
resultOldWay.put("최고점", maxScoreStudent);
log.info("점수 통계: {}", resultOldWay);
// Java 12의 teeing 사용 방식
Map<String, Optional<Student>> resultNewWay = students.stream()
.collect(Collectors.teeing(
Collectors.maxBy(Comparator.comparing(Student::getScore)),
Collectors.minBy(Comparator.comparing(Student::getScore)),
(max, min) -> Map.of("최고점", max, "최저점", min)
));
log.info("new 점수 통계: {}", resultNewWay);
결과

- 스트림 한 번의 처리로 두 가지 결과를 도출할 수 있습니다.
- 평균과 표준편차, 최대값과 최소값 등 관련된 통계를 계산할 때 효율적입니다.
'Java' 카테고리의 다른 글
Java에서 if-else 문을 enum으로 대체하는 깔끔한 접근 방식 (0) | 2025.03.11 |
---|---|
예제로 알아보는 Java Stream API (0) | 2025.02.07 |
if-else vs switch vs enum 처리 속도 비교 (0) | 2025.02.03 |
[List] ArrayList 와 LinkedList (0) | 2021.03.18 |