Running (day3)

최길중·2026년 1월 19일

Step 1: "반복문, 이제 그만 놓아주세요" (스트림의 등장)

List<Idol> idols = List.of(
            new Idol("장원영", "IVE", 100),
            new Idol("안유진", "IVE", 95),
            new Idol("카즈하", "LE SSERAFIM", 90),
            new Idol("김채원", "LE SSERAFIM", 92),
            new Idol("닝닝", "AESPA", 88)
        );
// 1. 임시 저장소 만들기 (귀찮음)
List<Idol> iveMembers = new ArrayList<>();

// 2. 반복문 + 조건문 지옥 (어떻게 할 것인가?)
for (Idol idol : idols) {
    if ("IVE".equals(idol.getTeam())) {
        iveMembers.add(idol);
    }
}

// 3. 정렬하기 (코드가 너무 길어져서 생략...)
// Collections.sort(iveMembers, ...);

// 4. 또 반복문 돌려서 이름 뽑기
List<String> resultNames = new ArrayList<>();
for (Idol idol : iveMembers) {
    resultNames.add(idol.getName());
}
List<String> result = idols.stream()
    .filter(idol -> "IVE".equals(idol.getTeam()))       // 1. IVE만 남겨라 (Filtering)
    .sorted(Comparator.comparing(Idol::getScore))       // 2. 점수대로 줄 세워라 (Sorting)
    .map(Idol::getName)                                 // 3. 이름표만 떼라 (Mapping)
    .collect(Collectors.toList());                      // 4. 바구니에 담아라 (Collecting)

🔑 스트림의 3단계 흐름

스트림 코드는 항상 이 3박자를 따릅니다.

  1. 생성하기: idols.stream() (데이터의 흐름을 만든다)

  2. 가공하기 (중간 연산): filter, sorted, map (데이터를 깎고 다듬는다)

  3. 결과 만들기 (최종 연산): collect (최종 결과물을 포장한다)

Step 2: "데이터 공장의 기계들"

(중간 연산)

1. filter: "입구 컷!"

조건에 맞지 않는 데이터는 가차 없이 탈락시킵니다. if문 대신 씁니다.

// 점수가 90점 이상인 아이돌만 통과!
idols.stream()
    .filter(idol -> idol.getScore() >= 90)
    ...

2. distinct: "중복은 안 받아요"

똑같은 데이터가 여러 개 들어오면 하나만 남깁니다.

⚠️ 주의: 이게 작동하려면 Idol 클래스에 equals()와 hashCode()가 제대로 구현되어 있어야 합니다! (주민등록증 확인 같은 거죠)

idols.stream()
    .distinct() // 쌍둥이(중복 객체) 하나 제거
    ...

3. limit: "선착순!"

데이터가 아무리 많아도 딱 정해진 개수만큼만 통과시킵니다.

idols.stream()
    .limit(2) // "앞에 2명만 들어오세요~"
    ...

4. map: "변신!"

데이터의 형태를 바꿉니다. 사과를 주면 사과 주스로, 아이돌 객체를 주면 이름(String)으로!
가장 많이 쓰이는 연산 중 하나입니다.

// Idol 객체 -> String(이름 + "님")으로 변환
idols.stream()
    .map(idol -> idol.getName() + "님")
    ...

5. sorted: "줄 서세요"

데이터를 정해진 기준에 따라 정렬합니다.

// 람다를 쓰면 정렬도 한 줄 컷!
idols.stream()
    .sorted(Comparator.comparingInt(Idol::getScore)) // 점수 낮은 순
    ...

🧩 종합 선물 세트 (Chaining)

List<String> result = idols.stream()
    .filter(idol -> idol.getScore() >= 90)       // 1. 우등생 필터링
    .distinct()                                  // 2. 중복 제거
    .sorted(Comparator.comparing(Idol::getName)) // 3. 이름순 정렬
    .map(Idol::getName)                          // 4. 이름만 추출
    .limit(2)                                    // 5. 2명만 제한
    .collect(Collectors.toList());               // 6. 포장 (최종 연산)

Step 3: "결과의 미학" (최종 연산)

1. Reduce: "하나로 합체!"

int totalScore = idols.stream()
    .map(Idol::getScore)         // 1. 점수만 추출
    .reduce(0, (a, b) -> a + b); // 2. 0부터 시작해서 계속 더하기

2. GroupingBy: "헤쳐 모여!"

// Map<String, List<Idol>> 형태로 반환
Map<String, List<Idol>> byTeam = idols.stream()
    .collect(Collectors.groupingBy(Idol::getTeam));

[결과]
IVE: [장원영, 안유진, 레이]
Aespa: [카리나, 지젤]
LE SSERAFIM: [김채원, 카즈하, 은채]

3. PartitioningBy: "흑백 논리"

// Map<Boolean, List<Idol>> 형태로 반환
Map<Boolean, List<Idol>> isGenius = idols.stream()
    .collect(Collectors.partitioningBy(idol -> idol.getScore() >= 90));

[결과]
true (우등생): [장원영, 안유진, 카리나...]
false (노력파): [은채...]

Step 4: "한 번 더, 스트림!”

// 미션: "300칼로리보다 높은 요리 중, 처음 2개는 건너뛰고 3개만 보여줘!"
menuList.stream()
    .filter(m -> m.getCalories() > 300) // 1. 300칼로리 초과 필터
    .skip(2)                            // 2. 앞의 2개는 패스
    .limit(3)                           // 3. 딱 3개만 선정
    .forEach(System.out::println);
// 미션: "500칼로리 이상 메뉴들의 이름만 뽑아서 리스트로 만들기"
List<String> highCalorieNames = menuList.stream()
    .filter(m -> m.getCalories() >= 500)
    .map(Dish::getName) // 요리 객체 -> 이름(String)으로 변환!
    .collect(toList());

// 미션: "요리의 총 칼로리 합계 구하기" (숫자 전용 스트림 사용)
int totalCalories = menuList.stream()
    .mapToInt(Dish::getCalories) // IntStream으로 변환하여 최적화
    .sum();
// 채식주의자가 먹을 수 있는 요리가 하나라도 있나요?
boolean hasVeggie = menuList.stream()
    .anyMatch(Dish::isVegetarian); // 결과: true

// 모든 요리가 1000칼로리 미만인가요?
boolean isHealthy = menuList.stream()
    .allMatch(m -> m.getCalories() < 1000); // 결과: true
// 미션: "육류 요리 중 칼로리가 낮은 순으로 정렬하기"
menuList.stream()
    .filter(m -> m.getType() == DishType.MEAT)
    .sorted(Comparator.comparing(Dish::getCalories)) // 오름차순
    .forEach(System.out::println);

// 미션: "이름을 역순(Z-A)으로 정렬하기"
menuList.stream()
    .sorted(Comparator.comparing(Dish::getName).reversed()) // 내림차순
    .forEach(System.out::println);

점검퀴즈

        // 하 1. 연도별 필터링:2022년에 예약된 모든 여행 정보를 찾아 가격 오름차순으로 정렬하여 출력하세요.
        List<Booking> list = bookings.stream()
                .filter(b -> b.getYear() == 2022)
                .sorted(Comparator.comparingDouble(Booking -> Booking.getPrice()))
                .toList();// Booking::getPrice
        for (Booking booking : list) {
            System.out.println(booking);
        }
        System.out.println("---------------------------------");

        // 하 2. 고유값 추출:여행지가 포함된 모든 국가(Country)의 이름을 중복 없이 리스트로 만드세요.
        List<String> Country = bookings.stream()
                .map(b -> b.getDestination().getCity())
                .distinct()
                .collect(Collectors.toList());
        System.out.println(Country);
        System.out.println("---------------------------------");

        // 하 3. 데이터 변환:모든 여행자의 이메일 주소를 수집하여 알파벳 순서대로 정렬된 리스트를 반환하세요.
        List<String> Email = bookings.stream()
                .map(b -> b.getTraveler().getEmail())
                .distinct()
                .sorted()
                .collect(Collectors.toList());
        System.out.println(Email);
        System.out.println("---------------------------------");

        // 하 4. 단순 존재 확인:이탈리아(Italy)로 가는 여행 예약이 한 건이라도 있는지boolean값으로 확인하세요.
        boolean b1 = bookings.stream()
                .anyMatch(b -> "Italy".equals(b.getDestination().getCountry()));
        System.out.println(b1);
        System.out.println("---------------------------------");

        // 중 1. 조건부 매핑과 정렬:'Paris'로 예약된 모든 여행자를 찾아
        // 이름순으로 오름차순 정렬하여 출력하세요. (중복된 여행자는 한 번만 나오게 하세요.)
        List<Booking> collect = bookings.stream()
                .filter(b -> "Paris".equals(b.getDestination().getCity()))
                .sorted(Comparator.comparing(b -> b.getTraveler().getName()))
                .distinct()
                .toList();
        for (Booking booking : collect) {
            System.out.println(booking);
        }
        System.out.println("---------------------------------");

        // 중 2. 수치 합산:프랑스(France)로 가는 모든 여행의 총 가격 합계를 계산하세요.
        double sum = bookings.stream()
                .filter(b -> "France".equals(b.getDestination().getCountry()))
                .mapToDouble(Booking::getPrice)
                .sum();
        System.out.println(sum);
        System.out.println("---------------------------------");

        // 중 3. **최댓값과 최저값:**모든 예약 중 최고 가격을 찾고, 가장 저렴한 여행의 전체 정보를 출력하세요.
        //    - *주의: 데이터가 하나도 없을 경우를 대비해`orElse()`처리를 고민해 보세요.*
        double max = bookings.stream()
                .mapToDouble(Booking::getPrice)
                .max()
                .orElse(0);
        System.out.println("최고 가격: " + max);

        Booking min = bookings.stream()
                .min(Comparator.comparingDouble(Booking::getPrice)) // price 기준 최솟값 Booking 찾기
                .orElse(null);
        
        if (min == null) {System.out.println("예약 데이터가 없습니다.");}
        else {System.out.println("가장 저렴한 여행 정보: " + min);}
        System.out.println("---------------------------------");
profile
개발자가 되고 싶어요

0개의 댓글