Java에서 Stream partitioningBy로 리스트를 50 이상과 미만으로 나누는 작업은 생각보다 자주 등장합니다. 처음에는 단순히 숫자 배열을 둘로 쪼개는 예제로 보이지만, 익숙해지면 학생 점수 합격/불합격, 상품 재고 있음/없음처럼 객체 컬렉션에도 그대로 확장할 수 있습니다. 수업 실습에서 이 부분을 다룰 때도, 반복문으로는 이미 구현할 수 있는 로직을 Stream으로 어떻게 더 읽기 좋게 표현하는지에 초점을 맞추면 이해가 훨씬 빨라집니다.

먼저 실행해 보는 예제: 정수 리스트를 50 기준으로 분할
가장 기본이 되는 코드는 아래와 같습니다. 조건은 50 이상이면 true, 50 미만이면 false입니다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class PartitioningByExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 25, 35, 50, 55, 60, 45, 70, 30);
Map<Boolean, List<Integer>> result = numbers.stream()
.collect(Collectors.partitioningBy(n -> n >= 50));
System.out.println("원본 리스트: " + numbers);
System.out.println("50 이상: " + result.get(true));
System.out.println("50 미만: " + result.get(false));
}
}
실행 결과는 이렇게 나옵니다.
원본 리스트: [10, 25, 35, 50, 55, 60, 45, 70, 30]
50 이상: [50, 55, 60, 70]
50 미만: [10, 25, 35, 45, 30]
겉보기에는 한 줄짜리 코드처럼 보여도, 실제로는 세 단계가 연결되어 있습니다.
numbers.stream(): 리스트를 스트림으로 만든다Collectors.partitioningBy(...): 조건식의 참/거짓으로 나눈다collect(...): 최종 결과를Map<Boolean, List<Integer>>형태로 모은다
처음 배울 때는 문법을 외우기보다, “조건식의 결과가 true냐 false냐에 따라 두 통에 담는 작업”이라고 이해하는 편이 더 오래 남습니다.
partitioningBy가 반환하는 구조를 눈에 익혀야 합니다
이 메서드의 반환 타입은 보통 아래처럼 생깁니다.
Map<Boolean, List<Integer>>
즉, 키는 Boolean, 값은 해당 그룹에 속한 리스트입니다. 이번 예제에서는 내부적으로 다음과 같이 저장된다고 보면 됩니다.
true -> [50, 55, 60, 70]
false -> [10, 25, 35, 45, 30]
여기서 초보자가 가장 자주 묻는 부분이 하나 있습니다. “왜 굳이 true, false를 키로 꺼내야 하나요?”
이건 partitioningBy 자체가 단 하나의 조건식을 기준으로 양쪽 그룹을 동시에 보관하도록 설계되었기 때문입니다. n -> n >= 50라는 식이 있으면:
- 조건을 만족하면
true - 만족하지 않으면
false
이 기준으로 자동 분할됩니다.
그래서 아래 코드는 자연스럽습니다.
result.get(true); // 50 이상
result.get(false); // 50 미만
filter()와 비슷해 보여도 쓰임새가 다릅니다
수업 중에 filter()와 헷갈리는 경우가 정말 많습니다. 둘 다 조건식을 쓰기 때문에 비슷해 보이지만, 결과가 다릅니다.
예를 들어 50 이상만 필요하다면 filter()로 충분합니다.
List<Integer> overOrEqual50 = numbers.stream()
.filter(n -> n >= 50)
.toList();
하지만 50 이상과 50 미만을 동시에 얻고 싶다면 partitioningBy가 더 적합합니다.
Map<Boolean, List<Integer>> result = numbers.stream()
.collect(Collectors.partitioningBy(n -> n >= 50));
실무에서도 둘 중 하나를 기계적으로 고르기보다, 결과를 몇 갈래로 보관해야 하는지 먼저 판단합니다.
- 한쪽만 필요하다 →
filter() - 참/거짓 두 그룹이 모두 필요하다 →
partitioningBy()
groupingBy() 대신 partitioningBy()를 쓰는 이유
이 부분도 함께 정리해 두면 Stream API를 훨씬 덜 헷갈립니다.
groupingBy()는 여러 그룹으로 묶을 때 자주 사용합니다. 예를 들어 점수를 구간별로 나누거나, 문자열 길이별로 묶는 식입니다. 반면 partitioningBy()는 결과가 무조건 두 그룹입니다. 조건식의 결과가 참 또는 거짓밖에 없기 때문입니다.
예를 들어:
n >= 50→ 두 그룹student.getScore() >= 60→ 두 그룹product.getStock() > 0→ 두 그룹
이처럼 기준이 명확한 이진 분류라면 partitioningBy()가 의도를 더 잘 드러냅니다. 코드만 봐도 “아, 이건 둘로 나누는 거구나”가 바로 읽히는 장점이 있습니다.
수업에서 학생들이 가장 많이 실수하는 부분은 이 지점입니다
직접 정리해 보니, 이 개념에서 자주 틀리는 부분은 문법보다 조건의 경계값입니다. 특히 50 이상과 미만이라고 했을 때 50이 어느 쪽에 들어가는지 헷갈려 합니다.
n >= 50이면 50은true그룹n > 50이면 50은false그룹
실습 과제를 채점할 때도 오답의 상당수가 여기서 나옵니다. 코드 자체는 맞는데 문제에서 요구한 기준을 한 글자 다르게 작성한 경우죠.
또 하나는 result.get(true)와 result.get(false)를 반대로 해석하는 실수입니다. 람다식이 길어지면 “true가 뭘 의미하는지”를 잠깐 놓치기 쉬워집니다. 그래서 수업에서는 보통 조건을 먼저 한국어로 읽게 합니다.
student.getScore() >= 60- 점수가 60 이상이면
true - 따라서
result.get(true)는 합격자 목록
이렇게 말로 먼저 풀어 읽으면 코드 해석이 훨씬 안정적입니다.
객체 리스트로 바꾸면 활용도가 확 올라갑니다
정수 예제는 입문용으로 좋지만, 실제 연습은 객체 리스트에서 해야 감이 제대로 옵니다. 학생 점수를 기준으로 합격/불합격을 나누는 예제를 보겠습니다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Student {
private final String name;
private final int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
@Override
public String toString() {
return name + "(" + score + ")";
}
}
public class StudentPartitionExample {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("민수", 80),
new Student("지은", 55),
new Student("서준", 40),
new Student("하린", 90)
);
Map<Boolean, List<Student>> result = students.stream()
.collect(Collectors.partitioningBy(student -> student.getScore() >= 60));
System.out.println("합격: " + result.get(true));
System.out.println("불합격: " + result.get(false));
}
}
실행 결과 예시는 아래와 같습니다.
합격: [민수(80), 하린(90)]
불합격: [지은(55), 서준(40)]
정수 예제를 이해한 뒤 이 코드까지 직접 손으로 쳐보면, partitioningBy가 단순 문법이 아니라 조건 기반 분류 도구라는 점이 분명해집니다.
실습 문제: partitioningBy와 filter를 같이 비교해 보기
얕게 메서드 하나만 보고 끝내면 기억에 잘 남지 않아서, 수업에서는 보통 아래처럼 비교 실습을 같이 합니다.
문제
주어진 숫자 리스트에서 다음 작업을 수행해 보세요.
1. partitioningBy로 50 이상과 50 미만으로 나눈다. 2. filter로 50 이상인 값만 따로 추출한다. 3. sorted를 추가해 50 이상 그룹을 오름차순으로 다시 확인한다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamPractice {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(70, 10, 55, 35, 60, 25, 50, 45, 30);
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n >= 50));
List<Integer> filteredAndSorted = numbers.stream()
.filter(n -> n >= 50)
.sorted()
.toList();
System.out.println("분할 결과 - 50 이상: " + partitioned.get(true));
System.out.println("분할 결과 - 50 미만: " + partitioned.get(false));
System.out.println("필터 + 정렬 결과: " + filteredAndSorted);
}
}
출력 예시:
분할 결과 - 50 이상: [70, 55, 60, 50]
분할 결과 - 50 미만: [10, 35, 25, 45, 30]
필터 + 정렬 결과: [50, 55, 60, 70]
여기서 확인할 포인트는 두 가지입니다.
partitioningBy는 원래 순서를 유지한 채 두 그룹을 동시에 보관한다.filter().sorted()는 특정 그룹만 골라 추가 가공할 때 편하다.
즉, Stream 메서드는 경쟁 관계라기보다 조합해서 쓰는 도구에 가깝습니다.
실제로 코드를 작성할 때 점검하면 좋은 것들
짧은 예제라도 아래 항목은 한번씩 확인하는 습관이 좋습니다.
- 기준값이
50 이상인지50 초과인지 문제 요구사항과 일치하는가 - 결과를 둘 다 써야 하는가, 아니면 한쪽만 필요한가
- 참/거짓의 의미를 코드만 봐도 알 수 있는가
- 객체 리스트라면 어떤 필드를 기준으로 나누는지 분명한가
필요하다면 변수명을 조금 더 드러나게 작성하는 것도 도움이 됩니다.
Map<Boolean, List<Integer>> partitionByScore = numbers.stream()
.collect(Collectors.partitioningBy(n -> n >= 50));
이름이 길어도 의미가 선명하면 나중에 다시 읽을 때 훨씬 편합니다.
참고 자료
- [Oracle Java Stream API](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html)
- [Oracle Collectors 관련 공식 문서 설명이 포함된 Stream API 문서](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/Stream.html)

저는 대학에서 컴퓨터 관련 학과 학생들을 가르치고 있는 교수 김기태입니다. Spring Boot, React, Python 등을 활용한 풀스택 웹 개발과 최신 AI 서비스 설계를 주로 다루고 있으며, 학생들이 현업 실무에 바로 적용할 수 있는 기술 역량을 키울 수 있도록 강의와 실습을 설계하고 있습니다.


