[멋사 클라우드 5기] Day 7 - Lambda · Stream 심화 + Optional

2026. 1. 30. 03:03·Learning Log

어제에 이어 오늘도 람다와 스트림을 중심으로 실습 위주 학습을 진행했다.
여기에 Comparable / Comparator, Optional 까지 이어지면서 개념이 빠르게 확장되는 느낌이었다.

오늘도 복기를 위한 정리를 진행해본다.


1. Stream 복습 & 실습

스트림 핵심 정리

  • 스트림은 일회성이다 (한 번 소비하면 재사용 불가)
  • 중간 연산은 지연 처리된다 (최종 연산 전까지 실행되지 않음)
  • 최종 연산이 실행되어야 파이프라인이 동작한다

Product 리스트 실습

List<Product> productList = Arrays.asList(
    new Product("potato", 18, 101),
    new Product("coke", 20, 102),
    new Product("granola", 55, 103),
    new Product("orange", 18, 104),
    new Product("melon", 32, 105)
);

가격 합계 구하기 (3가지 방식)

같은 결과라도 “상황에 따라 더 읽기 좋은 방식”이 달라서 여러 방법이 존재한다.

int sum1 = productList.stream()
    .mapToInt(Product::getPrice)
    .sum();

int sum2 = productList.stream()
    .map(Product::getPrice)
    .reduce(0, Integer::sum);

int sum3 = productList.stream()
    .collect(Collectors.summingInt(Product::getPrice));
  • mapToInt().sum() : 기본형 스트림(IntStream)을 활용해서 가장 직관적
  • reduce() : 누적 연산의 원리를 보여주는 느낌 (학습용으로 좋음)
  • Collectors.summingInt() : collect 흐름 안에서 “합계”를 표현할 때 깔끔함

평균 + 통계 정보

double avg = productList.stream()
    .collect(Collectors.averagingDouble(Product::getPrice));

DoubleSummaryStatistics stats = productList.stream()
    .mapToDouble(Product::getPrice)
    .summaryStatistics();

summaryStatistics()는 count/sum/min/max/avg를 한 번에 뽑아줘서 유용하다.


groupingBy + mapping

Map<Integer, List<String>> grouped = productList.stream()
    .collect(Collectors.groupingBy(
        Product::getProductCode,
        Collectors.mapping(Product::getName, Collectors.toList())
    ));
  • groupingBy : 기준으로 묶기
  • mapping : 묶되, 객체 전체 말고 필요한 값만 뽑아서 담기

2. 스트림은 어떻게 실행될까?

스트림은 “전체를 한 번에 처리”하는 느낌이 아니라, 요소 하나를 흘려보내며 단계별로 처리한다.
그리고 중간 연산은 최종 연산이 호출되기 전까지는 실행되지 않는다(지연 처리).

lectureList.stream()
    .filter(v -> {
        System.out.println("filter");
        return v.contains("a");
    })
    .map(v -> {
        System.out.println("map");
        return v;
    })
    .collect(Collectors.toList());

출력 결과를 통해 확인한 점:

  • 최종 연산(collect)이 호출되어야 전체 파이프라인이 실행된다
  • 각 요소는 filter → map 순서로 처리되고, 다음 요소로 넘어간다
    (즉, filter를 전부 돌리고 map을 도는 구조가 아니다)

3. Comparable vs Comparator

객체 리스트를 정렬하려다 보면 가장 먼저 마주치는 개념이 Comparable과 Comparator이다.
두 인터페이스의 차이는 “누가 정렬 기준을 가지고 있느냐”로 정리하면 명확하다.


Comparable — 객체 스스로의 기본 정렬 기준

Comparable은 객체 자신이 기본 정렬 기준을 알고 있을 때 사용한다.

public interface Comparable<T> {
    int compareTo(T o);
}
  • 객체 내부에 기본 정렬 기준을 정의
  • 기본 정렬 기준은 보통 1개로 고정되는 편
  • Comparator.naturalOrder(), Comparator.reverseOrder()와 함께 잘 맞는다
public class Product implements Comparable<Product> {
    private int productCode;

    @Override
    public int compareTo(Product o) {
        return this.productCode - o.productCode;
    }
}
productList.sort(Comparator.naturalOrder());
productList.sort(Comparator.reverseOrder());

Comparator — 외부에서 비교 기준을 주입

Comparator는 객체 외부에서 정렬 기준을 정의한다.

public interface Comparator<T> {
    int compare(T o1, T o2);
}
  • 객체 수정 없이 정렬 로직 분리 가능
  • 정렬 기준이 다양할 때 유리
  • 실무에서 더 자주 보이는 방식
Comparator<Product> byCodeDesc =
    (a, b) -> b.getProductCode() - a.getProductCode();

productList.sort(byCodeDesc);

스트림에서도 동일하게 사용 가능하다.

productList.stream()
    .sorted((a, b) -> b.getProductCode() - a.getProductCode())
    .forEach(System.out::println);

언제 무엇을 쓸까?

  • “이 객체의 기본 정렬은 이거다”가 명확하면 → Comparable
  • 정렬 기준이 바뀔 수 있거나 여러 기준이 필요하면 → Comparator

보통은
👉 Comparator 중심 + 필요하면 Comparable 보조로 사용한다고 한다.


4. Optional

Optional이 등장한 배경

자바에서 null은 너무 자유롭고, 그만큼 위험하다.

obj.method(); // obj가 null이면 NPE

Optional은 값이 없을 수도 있음을 명시적으로 표현하기 위한 클래스다.


Optional 생성

Optional.empty();              // 빈 Optional
Optional.of(value);            // null ❌
Optional.ofNullable(value);    // null ⭕

값 꺼내기

opt.ifPresent(v -> ...);

opt.orElse("default");
opt.orElseGet(() -> "default"); // lazy, 권장
opt.orElseThrow(() -> new Exception());

orElse vs orElseGet

opt.orElse(expensive());           // 항상 실행
opt.orElseGet(() -> expensive());  // 필요할 때만 실행

👉 기본적으로 orElseGet이 안전하다.


Optional + map

Optional<String> opt = Optional.ofNullable("optional");
Optional<Integer> len = opt.map(String::length);
// Optional[8]

Optional Best Practice

Optional<User> user = Optional.empty(); // OK
Optional<User> user = null;             // ❌
  • Optional을 필드/파라미터/생성자에 쓰는 건 보통 비권장
  • 반환값에서 “없을 수 있음”을 표현할 때 가장 깔끔하다
  • 컬렉션을 Optional로 감싸는 것도 보통 피한다
    (Optional<List<T>> 대신 Collections.emptyList() 같은 방식)

5. Optional + MVC 개선 적용

Model은 핵심 로직에 집중하고, 예외 처리는 Controller가 담당하도록 정리해봤다.

Model — 조회 실패 시 예외 던지기

return studentList.stream()
    .filter(Objects::nonNull)
    .filter(s -> s.getStudentNo() == studentNo)
    .findFirst()
    .orElseThrow(() -> new Exception("일치하는 학생이 없습니다."));

Controller — 예외 처리

try {
    Student student = StudentModel.getStudentByStudentNo(1001);
    EndView.printStudent(student);
} catch (Exception e) {
    EndView.printResult(e.getMessage());
}
  • Model: 조회 로직/규칙
  • Controller: 흐름 제어 + 예외 처리

'Learning Log' 카테고리의 다른 글

[멋사 클라우드 5기] Day 9 - Enum 활용, BigNumber, Lombok, 객체 설계  (1) 2026.02.03
[멋사 클라우드 5기] Day 8 - 입출력, 직렬화, 스레드, Enum  (0) 2026.02.02
[멋사 클라우드 5기] Day 6 - 자료구조, Lambda, Stream  (0) 2026.01.29
[멋사 클라우드 5기] Day 5 - Java 기본 라이브러리 & 컬렉션 정리  (0) 2026.01.28
equals가 있는데 hashCode는 왜 필요할까?  (0) 2026.01.27
'Learning Log' 카테고리의 다른 글
  • [멋사 클라우드 5기] Day 9 - Enum 활용, BigNumber, Lombok, 객체 설계
  • [멋사 클라우드 5기] Day 8 - 입출력, 직렬화, 스레드, Enum
  • [멋사 클라우드 5기] Day 6 - 자료구조, Lambda, Stream
  • [멋사 클라우드 5기] Day 5 - Java 기본 라이브러리 & 컬렉션 정리
allluck777
allluck777
allluck777
    • 분류 전체보기 (44) N
      • AWS (0)
      • Network (0)
      • Linux (0)
      • Docker (0)
      • Project (4)
        • CloudNote (4)
      • Learning Log (36) N
      • Lecture (3)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (3)
  • 전체
    오늘
    어제
  • hELLO· Designed By정상우.v4.10.6
allluck777
[멋사 클라우드 5기] Day 7 - Lambda · Stream 심화 + Optional
상단으로

티스토리툴바