본문 바로가기
쿤즈 Dev/Java

[자바 스트림 10] min·max 최솟값·최댓값 — 기준 하나로 극값 뽑기

by Koonz:) 2026. 10. 26.
728x90
요약: min·max는 비교 기준(Comparator)에 따라 가장 작은/큰 요소를 찾는 최종 연산입니다. 숫자의 극값은 물론, "가장 긴 문자열", "가장 비싼 상품"처럼 객체의 극값도 찾을 수 있죠. 이 글에서 사용법, Comparator 지정, 결과가 Optional로 감싸지는 이유, 그리고 숫자 스트림의 축약형을 정리합니다. 파트1 기초편의 마지막입니다.

 

"최고 점수는?", "가장 싼 상품은?", "제일 최근 주문은?" — 극값(min/max) 찾기도 집계만큼이나 자주 나옵니다. for 루프로 "현재 최댓값 변수"를 두고 하나씩 비교하던 그 로직을, 스트림은 max(Comparator) 한 줄로 끝냅니다.

 

중요한 건 min·max도 Optional을 반환한다는 점입니다. 스트림이 비어 있으면 "가장 큰 값"이 존재하지 않으니까요. 6편의 average()가 OptionalDouble이었던 것과 같은 맥락입니다. 면접에서 "min/max의 반환 타입은?"이 함정으로 나옵니다.

 

자바 스트림 min max 최솟값 최댓값 파트1 완결

 

문제 정의

 

문제. ① 정수 리스트의 최솟값·최댓값을 구하고, ② 문자열 리스트에서 가장 긴 문자열을 찾아라.

 

  • 입력:
  • List<Integer> nums = [42, 17, 88, 5, 63]
  • List<String> words = ["java", "stream", "api", "lambda"]
  • 기대 출력:
  • 최솟값: 5
  • 최댓값: 88
  • 가장 긴 문자열: stream

 

전통 방식(for-loop) — 그리고 스트림이 나은 이유

 

import java.util.List;

public class Traditional {
    public static void main(String[] args) {
        List<Integer> nums = List.of(42, 17, 88, 5, 63);

        int min = nums.get(0);
        int max = nums.get(0);
        for (int n : nums) {
            if (n < min) min = n;    // 최솟값 갱신
            if (n > max) max = n;    // 최댓값 갱신
        }
        System.out.println(min);
        System.out.println(max);
    }
}

 

"초깃값을 첫 요소로 두고, 돌면서 갱신"하는 패턴입니다. 빈 리스트면 get(0)에서 예외가 나고, 객체 기준 비교로 바뀌면 코드가 더 길어집니다. 스트림 min·max는 비교 기준만 넘기면 되고, 빈 경우도 Optional로 안전하게 다룹니다.

 

스트림 해법 — min · max

 

자바 스트림 min max 최솟값 최댓값 Optional

 

import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class MinMaxExample {
    public static void main(String[] args) {
        List<Integer> nums = List.of(42, 17, 88, 5, 63);

        // ① 최솟값·최댓값 (자연 순서 기준)
        Optional<Integer> min = nums.stream().min(Comparator.naturalOrder());
        Optional<Integer> max = nums.stream().max(Comparator.naturalOrder());
        System.out.println(min.get());
        System.out.println(max.get());

        // ② 객체 극값: 가장 긴 문자열
        List<String> words = List.of("java", "stream", "api", "lambda");
        Optional<String> longest = words.stream()
                                        .max(Comparator.comparingInt(String::length));
        System.out.println(longest.orElse("(없음)"));

        // (보너스) 숫자 스트림은 IntStream의 min/max가 더 간결
        int intMin = nums.stream().mapToInt(Integer::intValue).min().getAsInt();
        System.out.println(intMin);
    }
}

 

한 줄씩 설명

 

  • .min(Comparator.naturalOrder()) — 자연 순서 기준으로 가장 작은 요소를 찾습니다. Comparator.naturalOrder()는 "작은 값이 먼저"라는 기본 비교자입니다.
  • .max(Comparator.naturalOrder()) — 같은 기준으로 가장 큰 요소를 찾습니다.
  • .max(Comparator.comparingInt(String::length)) — "길이가 가장 큰" 문자열을 찾습니다. 기준을 Comparator로 바꾸면 어떤 필드로든 극값을 구할 수 있습니다. (java·api도 있지만 stream·lambda가 6글자로 가장 길고, 먼저 나온 stream이 선택됩니다.)
  • mapToInt(...).min().getAsInt() — 숫자라면 IntStream의 min()이 더 간결합니다. 이때 반환은 OptionalInt이고 getAsInt()로 꺼냅니다.

 

실행 결과

 

5
88
stream
5

 

변형·함정·주의

 

  • ⚠ 반환은 Optional. min·max는 빈 스트림일 수 있어 Optional<T>(기본형은 OptionalInt 등)을 반환합니다. get() 전에 isPresent()를 확인하거나 orElse(기본값)로 안전하게 꺼내세요. 빈 스트림에 get()하면 NoSuchElementException입니다.
  • 동점 처리. 비교 기준으로 같은 값이 여러 개면 보통 먼저 만난 요소가 선택됩니다(구현 의존이 아닌, 안정적 동작). 특정 요소를 원하면 thenComparing으로 2차 기준을 더하세요.
  • min은 max의 역. max(comp)는 min(comp.reversed())와 같습니다. 기준 하나만 뒤집으면 방향이 바뀝니다.
  • 집계와의 관계. 숫자 극값만 필요하면 6편의 summaryStatistics()가 getMin()·getMax()를 한 번의 순회로 함께 줍니다. min·max·sum·average를 모두 쓸 땐 이쪽이 효율적입니다.

 

관련 면접 질문

 

  • Q1. min·max의 반환 타입이 Optional인 이유는?

 

→ 스트림이 비어 있으면 극값이 존재하지 않기 때문입니다. Optional로 감싸 "값이 없을 수 있음"을 타입으로 표현하고, NoSuchElementException을 예방합니다.

 

  • Q2. 객체 리스트에서 특정 필드 기준 최댓값을 찾으려면?

 

→ stream.max(Comparator.comparing(Obj::getField))를 씁니다. 숫자 필드는 comparingInt/comparingDouble이 박싱이 없어 더 낫고, 동점 시 2차 기준은 thenComparing으로 지정합니다.

 

시리즈 네비게이션

 

  • ⬆️ 📎 목차 허브 — 자바 스트림 코딩 문제 41선 총정리  (0편으로 돌아가기)
  • ⬅️ 📎 이전 편 — [자바 스트림 9] limit·skip 페이징
  • ➡️ 📎 다음 편 — [자바 스트림 11] flatMap으로 중첩 평탄화 (파트2 중급 시작, 발행 시 연결)
  • 📎 함께 읽기 — Java 25 LTS 새 기능 총정리 (최신 자바 문법 레퍼런스)

 

🎉 이것으로 파트1 기초 10편이 끝났습니다. 스트림 생성·변환·필터·수집·집계·정렬·중복·페이징·극값까지, 실무 코드의 90%를 읽을 기본기를 갖추셨습니다. 다음 편부터는 파트2 중급 — flatMap·reduce·groupingBy 같은 코테·면접의 진짜 핵심이 이어집니다.

댓글