
알고리즘 문제를 풀 때 자주 사용하는 Java Stream API의 주요 메서드를 정리했습니다.
Stream이란? 컬렉션, 배열 등의 데이터를 함수형 프로그래밍 방식으로 처리할 수 있는 API (Java 8+)
1. Stream 생성
1.1 컬렉션에서 생성
// Collection 인터페이스
Stream<E> stream() // 순차 스트림
Stream<E> parallelStream() // 병렬 스트림
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream1 = list.stream();
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c"));
Stream<String> stream2 = set.stream();
Map<String, Integer> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
1.2 배열에서 생성
// Arrays 클래스
static <T> Stream<T> stream(T[] array)
static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
static IntStream stream(int[] array)
static IntStream stream(int[] array, int startInclusive, int endExclusive)
static LongStream stream(long[] array)
static LongStream stream(long[] array, int startInclusive, int endExclusive)
static DoubleStream stream(double[] array)
static DoubleStream stream(double[] array, int startInclusive, int endExclusive)
사용 예시
String[] arr = {"a", "b", "c"};
Stream<String> stream1 = Arrays.stream(arr);
Stream<String> stream2 = Arrays.stream(arr, 0, 2); // "a", "b"
int[] intArr = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArr);
1.3 Stream.of()
static <T> Stream<T> of(T... values) // 가변 인자
static <T> Stream<T> of(T t) // 단일 요소
static <T> Stream<T> empty() // 빈 스트림
사용 예시
Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);
Stream<String> empty = Stream.empty();
1.4 범위 생성 (IntStream, LongStream)
// IntStream
static IntStream range(int startInclusive, int endExclusive) // [start, end)
static IntStream rangeClosed(int startInclusive, int endInclusive) // [start, end]
// LongStream
static LongStream range(long startInclusive, long endExclusive)
static LongStream rangeClosed(long startInclusive, long endInclusive)
사용 예시
IntStream stream1 = IntStream.range(1, 5); // 1, 2, 3, 4
IntStream stream2 = IntStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5
// 반복문 대체
IntStream.range(0, 10).forEach(i -> System.out.println(i));
// 배열 인덱스 순회
String[] arr = {"a", "b", "c"};
IntStream.range(0, arr.length).forEach(i -> System.out.println(arr[i]));
1.5 무한 스트림
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
static <T> Stream<T> generate(Supplier<? extends T> s)
// IntStream
static IntStream iterate(int seed, IntUnaryOperator f)
static IntStream iterate(int seed, IntPredicate hasNext, IntUnaryOperator next)
static IntStream generate(IntSupplier s)
사용 예시
// 무한 수열 (limit으로 제한)
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
infiniteStream.limit(10).forEach(System.out::println); // 0~9
// 조건부 무한 수열 (Java 9+)
Stream<Integer> stream = Stream.iterate(0, n -> n < 10, n -> n + 1); // 0~9
// generate
Stream<Double> randomStream = Stream.generate(Math::random);
randomStream.limit(5).forEach(System.out::println);
// 피보나치 수열
Stream.iterate(new int[]{0, 1}, arr -> new int[]{arr[1], arr[0] + arr[1]})
.limit(10)
.map(arr -> arr[0])
.forEach(System.out::println);
1.6 기타
// Stream.Builder
Stream.Builder<T> builder()
Stream.Builder<T> add(T t)
Stream<T> build()
// Files (파일 읽기)
static Stream<String> lines(Path path)
사용 예시
// Builder 패턴
Stream<String> stream = Stream.<String>builder()
.add("a")
.add("b")
.add("c")
.build();
2. 중간 연산 (Intermediate Operations)
중간 연산은 Stream을 반환하므로 체이닝이 가능합니다. **지연 연산(lazy)**되어 최종 연산이 호출될 때 실행됩니다.
2.1 필터링
filter
Stream<T> filter(Predicate<? super T> predicate)
IntStream filter(IntPredicate predicate)
LongStream filter(LongPredicate predicate)
DoubleStream filter(DoublePredicate predicate)
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 짝수만 필터링
List<Integer> evenNumbers = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList()); // [2, 4, 6, 8, 10]
// 문자열 길이 필터링
List<String> words = Arrays.asList("apple", "hi", "banana", "ok");
List<String> longWords = words.stream()
.filter(w -> w.length() > 2)
.collect(Collectors.toList()); // ["apple", "banana"]
2.2 중복 제거
distinct
Stream<T> distinct()
IntStream distinct()
LongStream distinct()
DoubleStream distinct()
사용 예시
List<Integer> list = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5);
List<Integer> unique = list.stream()
.distinct()
.collect(Collectors.toList()); // [1, 2, 3, 4, 5]
2.3 정렬
sorted
Stream<T> sorted() // 자연 순서
Stream<T> sorted(Comparator<? super T> comparator) // 사용자 정의 순서
IntStream sorted()
LongStream sorted()
DoubleStream sorted()
사용 예시
List<Integer> list = Arrays.asList(5, 2, 8, 1, 9);
// 오름차순
List<Integer> sorted1 = list.stream()
.sorted()
.collect(Collectors.toList()); // [1, 2, 5, 8, 9]
// 내림차순
List<Integer> sorted2 = list.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList()); // [9, 8, 5, 2, 1]
// 문자열 길이순
List<String> words = Arrays.asList("apple", "hi", "banana");
List<String> sorted3 = words.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList()); // ["hi", "apple", "banana"]
// 복합 정렬
List<String> sorted4 = words.stream()
.sorted(Comparator.comparing(String::length)
.thenComparing(Comparator.naturalOrder()))
.collect(Collectors.toList());
2.4 변환 (매핑)
map
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
// IntStream
IntStream map(IntUnaryOperator mapper)
<U> Stream<U> mapToObj(IntFunction<? extends U> mapper)
LongStream mapToLong(IntToLongFunction mapper)
DoubleStream mapToDouble(IntToDoubleFunction mapper)
사용 예시
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 대문자로 변환
List<String> upper = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList()); // ["APPLE", "BANANA", "CHERRY"]
// 길이로 변환
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList()); // [5, 6, 6]
// 제곱
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList()); // [1, 4, 9, 16, 25]
// IntStream으로 변환
int[] arr = words.stream()
.mapToInt(String::length)
.toArray(); // [5, 6, 6]
flatMap (중첩 구조를 평탄화)
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper)
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper)
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper)
// IntStream
IntStream flatMap(IntFunction<? extends IntStream> mapper)
사용 예시
// 2차원 리스트를 1차원으로
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList()); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 문자열을 문자로 분해
List<String> words = Arrays.asList("hello", "world");
List<Character> chars = words.stream()
.flatMap(word -> word.chars().mapToObj(c -> (char)c))
.collect(Collectors.toList()); // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
// 공백으로 분할
String text = "apple banana cherry";
List<String> wordList = Arrays.stream(text.split(" "))
.flatMap(word -> Arrays.stream(word.split("")))
.collect(Collectors.toList());
2.5 제한 및 건너뛰기
limit, skip
Stream<T> limit(long maxSize) // 최대 maxSize개만
Stream<T> skip(long n) // 앞의 n개 건너뛰기
IntStream limit(long maxSize)
IntStream skip(long n)
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 처음 5개만
List<Integer> first5 = list.stream()
.limit(5)
.collect(Collectors.toList()); // [1, 2, 3, 4, 5]
// 처음 3개 건너뛰고
List<Integer> skip3 = list.stream()
.skip(3)
.collect(Collectors.toList()); // [4, 5, 6, 7, 8, 9, 10]
// 건너뛰고 제한 (페이징)
List<Integer> page2 = list.stream()
.skip(5)
.limit(5)
.collect(Collectors.toList()); // [6, 7, 8, 9, 10]
takeWhile, dropWhile (Java 9+)
Stream<T> takeWhile(Predicate<? super T> predicate) // 조건이 true인 동안만
Stream<T> dropWhile(Predicate<? super T> predicate) // 조건이 true인 동안 건너뜀
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 1, 2);
// 5 미만인 동안만 (정렬된 스트림에서 유용)
List<Integer> taken = list.stream()
.takeWhile(n -> n < 5)
.collect(Collectors.toList()); // [1, 2, 3, 4]
// 5 미만인 동안 건너뛰기
List<Integer> dropped = list.stream()
.dropWhile(n -> n < 5)
.collect(Collectors.toList()); // [5, 1, 2]
2.6 엿보기 (디버깅용)
peek
Stream<T> peek(Consumer<? super T> action)
IntStream peek(IntConsumer action)
사용 예시
List<Integer> result = list.stream()
.filter(n -> n % 2 == 0)
.peek(n -> System.out.println("Filtered: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("Mapped: " + n))
.collect(Collectors.toList());
2.7 기타
boxed, unboxed
// IntStream, LongStream, DoubleStream
Stream<Integer> boxed() // int → Integer
Stream<Long> boxed() // long → Long
Stream<Double> boxed() // double → Double
사용 예시
int[] arr = {1, 2, 3, 4, 5};
List<Integer> list = Arrays.stream(arr)
.boxed()
.collect(Collectors.toList());
3. 최종 연산 (Terminal Operations)
최종 연산은 Stream을 소비하고 결과를 반환합니다. 한 번만 사용 가능합니다.
3.1 반복
forEach, forEachOrdered
void forEach(Consumer<? super T> action)
void forEachOrdered(Consumer<? super T> action) // 순서 보장 (병렬 스트림에서도)
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream().forEach(System.out::println);
// 병렬 스트림에서 순서 보장
list.parallelStream().forEachOrdered(System.out::println);
3.2 수집
collect
<R, A> R collect(Collector<? super T, A, R> collector)
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
사용 예시 (Collectors와 함께)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// List로 수집
List<Integer> result1 = list.stream().collect(Collectors.toList());
// Set으로 수집
Set<Integer> result2 = list.stream().collect(Collectors.toSet());
// 배열로 수집
Integer[] result3 = list.stream().toArray(Integer[]::new);
int[] result4 = list.stream().mapToInt(Integer::intValue).toArray();
// 문자열로 결합
String result5 = list.stream()
.map(String::valueOf)
.collect(Collectors.joining(", ")); // "1, 2, 3, 4, 5"
toArray
Object[] toArray()
<A> A[] toArray(IntFunction<A[]> generator)
// IntStream, LongStream, DoubleStream
int[] toArray()
long[] toArray()
double[] toArray()
사용 예시
String[] arr1 = list.stream().toArray(String[]::new);
Integer[] arr2 = list.stream().toArray(Integer[]::new);
int[] arr3 = list.stream().mapToInt(Integer::intValue).toArray();
3.3 매칭
boolean anyMatch(Predicate<? super T> predicate) // 하나라도 만족
boolean allMatch(Predicate<? super T> predicate) // 모두 만족
boolean noneMatch(Predicate<? super T> predicate) // 모두 불만족
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
boolean hasEven = list.stream().anyMatch(n -> n % 2 == 0); // true
boolean allPositive = list.stream().allMatch(n -> n > 0); // true
boolean noneNegative = list.stream().noneMatch(n -> n < 0); // true
3.4 찾기
Optional<T> findFirst() // 첫 번째 요소
Optional<T> findAny() // 아무 요소 (병렬에서 빠름)
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> first = list.stream()
.filter(n -> n > 3)
.findFirst(); // Optional[4]
Integer value = first.orElse(-1); // 4
Integer value2 = first.orElseGet(() -> -1);
Integer value3 = first.orElseThrow(() -> new RuntimeException("Not found"));
// 병렬 스트림
Optional<Integer> any = list.parallelStream()
.filter(n -> n > 3)
.findAny(); // 4 또는 5 (순서 보장 안됨)
3.5 집계
count
long count()
min, max
Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
// IntStream, LongStream, DoubleStream
OptionalInt min()
OptionalInt max()
OptionalLong min()
OptionalLong max()
OptionalDouble min()
OptionalDouble max()
sum, average (IntStream, LongStream, DoubleStream)
int sum() // IntStream
long sum() // LongStream
double sum() // DoubleStream
OptionalDouble average() // 모든 기본 타입 스트림
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// count
long count = list.stream().count(); // 5
// min, max
Optional<Integer> min = list.stream().min(Comparator.naturalOrder()); // 1
Optional<Integer> max = list.stream().max(Comparator.naturalOrder()); // 5
// sum, average (IntStream)
int sum = list.stream().mapToInt(Integer::intValue).sum(); // 15
double avg = list.stream().mapToInt(Integer::intValue).average().orElse(0); // 3.0
// 배열에서
int[] arr = {1, 2, 3, 4, 5};
int sum2 = Arrays.stream(arr).sum();
double avg2 = Arrays.stream(arr).average().getAsDouble();
int min2 = Arrays.stream(arr).min().getAsInt();
int max2 = Arrays.stream(arr).max().getAsInt();
3.6 축약 (reduce)
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
// IntStream, LongStream, DoubleStream
OptionalInt reduce(IntBinaryOperator op)
int reduce(int identity, IntBinaryOperator op)
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 합계
int sum1 = list.stream().reduce(0, (a, b) -> a + b); // 15
int sum2 = list.stream().reduce(0, Integer::sum); // 15
// 곱셈
int product = list.stream().reduce(1, (a, b) -> a * b); // 120
// 최대값
Optional<Integer> max = list.stream().reduce(Integer::max); // 5
// 문자열 연결
String concat = list.stream()
.map(String::valueOf)
.reduce("", (a, b) -> a + b); // "12345"
4. Collectors 유틸리티
java.util.stream.Collectors 클래스는 다양한 수집 연산을 제공합니다.
4.1 기본 수집
static <T> Collector<T, ?, List<T>> toList()
static <T> Collector<T, ?, Set<T>> toSet()
static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory)
static <T> Collector<T, ?, List<T>> toUnmodifiableList() // Java 10+
static <T> Collector<T, ?, Set<T>> toUnmodifiableSet()
사용 예시
List<Integer> list = stream.collect(Collectors.toList());
Set<Integer> set = stream.collect(Collectors.toSet());
ArrayList<Integer> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
TreeSet<Integer> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));
4.2 문자열 결합
static Collector<CharSequence, ?, String> joining()
static Collector<CharSequence, ?, String> joining(CharSequence delimiter)
static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
사용 예시
List<String> words = Arrays.asList("apple", "banana", "cherry");
String result1 = words.stream().collect(Collectors.joining()); // "applebananacherry"
String result2 = words.stream().collect(Collectors.joining(", ")); // "apple, banana, cherry"
String result3 = words.stream().collect(Collectors.joining(", ", "[", "]")); // "[apple, banana, cherry]"
4.3 집계
static <T> Collector<T, ?, Long> counting()
static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator)
static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator)
static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper)
static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper)
static <T> Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper)
static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper)
static <T> Collector<T, ?, Double> averagingLong(ToLongFunction<? super T> mapper)
static <T> Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper)
static <T> Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
long count = list.stream().collect(Collectors.counting()); // 5
Optional<Integer> min = list.stream().collect(Collectors.minBy(Comparator.naturalOrder())); // 1
Optional<Integer> max = list.stream().collect(Collectors.maxBy(Comparator.naturalOrder())); // 5
int sum = list.stream().collect(Collectors.summingInt(Integer::intValue)); // 15
double avg = list.stream().collect(Collectors.averagingInt(Integer::intValue)); // 3.0
// 통계 정보 한번에
IntSummaryStatistics stats = list.stream().collect(Collectors.summarizingInt(Integer::intValue));
System.out.println(stats.getCount()); // 5
System.out.println(stats.getSum()); // 15
System.out.println(stats.getMin()); // 1
System.out.println(stats.getMax()); // 5
System.out.println(stats.getAverage()); // 3.0
4.4 그룹화
static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier)
static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)
static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream)
사용 예시
List<String> words = Arrays.asList("apple", "banana", "cherry", "avocado", "blueberry");
// 첫 글자로 그룹화
Map<Character, List<String>> grouped = words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0)));
// {a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
// 길이로 그룹화
Map<Integer, List<String>> byLength = words.stream()
.collect(Collectors.groupingBy(String::length));
// {5=[apple], 6=[banana, cherry], 7=[avocado], 9=[blueberry]}
// 그룹화 + 카운팅
Map<Integer, Long> lengthCount = words.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
// {5=1, 6=2, 7=1, 9=1}
// 그룹화 + Set으로 수집
Map<Character, Set<String>> groupedSet = words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0), Collectors.toSet()));
// 짝수/홀수 그룹화
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<Boolean, List<Integer>> evenOdd = numbers.stream()
.collect(Collectors.groupingBy(n -> n % 2 == 0));
// {false=[1, 3, 5], true=[2, 4, 6]}
4.5 분할 (partitioningBy)
static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate)
static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
사용 예시
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 짝수/홀수로 분할
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
List<Integer> evens = partitioned.get(true);
List<Integer> odds = partitioned.get(false);
// 분할 + 카운팅
Map<Boolean, Long> partitionedCount = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting()));
// {false=5, true=5}
4.6 Map으로 수집
static <T, K, U> Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
static <T, K, U> Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
사용 예시
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 단어 → 길이 맵
Map<String, Integer> wordLengths = words.stream()
.collect(Collectors.toMap(w -> w, String::length));
// {apple=5, banana=6, cherry=6}
// 또는
Map<String, Integer> wordLengths2 = words.stream()
.collect(Collectors.toMap(Function.identity(), String::length));
// 중복 키 처리 (mergeFunction)
List<String> duplicates = Arrays.asList("apple", "banana", "apple");
Map<String, Integer> counts = duplicates.stream()
.collect(Collectors.toMap(w -> w, w -> 1, Integer::sum));
// {apple=2, banana=1}
// TreeMap으로 수집
Map<String, Integer> treeMap = words.stream()
.collect(Collectors.toMap(w -> w, String::length, (v1, v2) -> v1, TreeMap::new));
4.7 기타
mapping
static <T, U, A, R> Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper, Collector<? super U, A, R> downstream)
filtering (Java 9+)
static <T, A, R> Collector<T, ?, R> filtering(Predicate<? super T> predicate, Collector<? super T, A, R> downstream)
collectingAndThen
static <T, A, R, RR> Collector<T, A, RR> collectingAndThen(Collector<T, A, R> downstream, Function<R, RR> finisher)
사용 예시
// mapping: 그룹화 후 변환
Map<Character, List<Integer>> grouped = words.stream()
.collect(Collectors.groupingBy(
w -> w.charAt(0),
Collectors.mapping(String::length, Collectors.toList())
));
// collectingAndThen: 수집 후 변환
List<Integer> unmodifiable = list.stream()
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
5. 병렬 스트림
Stream<T> parallel() // 병렬 스트림으로 변환
Stream<T> sequential() // 순차 스트림으로 변환
boolean isParallel() // 병렬 스트림인지 확인
사용 예시
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 병렬 스트림 생성
Stream<Integer> parallel1 = list.parallelStream();
Stream<Integer> parallel2 = list.stream().parallel();
// 큰 데이터셋에서 성능 향상
int sum = IntStream.range(1, 1000000)
.parallel()
.filter(n -> n % 2 == 0)
.sum();
// 주의: 순서가 중요하거나 작은 데이터셋에서는 오히려 느릴 수 있음
6. 알고리즘 문제 풀이 팁
6.1 자주 사용하는 패턴
1. 배열 → List
int[] arr = {1, 2, 3, 4, 5};
List<Integer> list = Arrays.stream(arr)
.boxed()
.collect(Collectors.toList());
2. List → 배열
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int[] arr = list.stream()
.mapToInt(Integer::intValue)
.toArray();
3. 문자열 → 문자 배열
String str = "hello";
char[] chars = str.chars()
.mapToObj(c -> (char)c)
.toArray(Character[]::new);
4. 중복 제거 + 정렬
List<Integer> list = Arrays.asList(5, 2, 8, 2, 1, 5, 9);
List<Integer> result = list.stream()
.distinct()
.sorted()
.collect(Collectors.toList()); // [1, 2, 5, 8, 9]
5. 빈도수 카운팅
List<String> words = Arrays.asList("a", "b", "a", "c", "a", "b");
Map<String, Long> frequency = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// {a=3, b=2, c=1}
6. 최대/최소 K개 요소
List<Integer> list = Arrays.asList(5, 2, 8, 1, 9, 3, 7);
// 최대 3개
List<Integer> top3 = list.stream()
.sorted(Comparator.reverseOrder())
.limit(3)
.collect(Collectors.toList()); // [9, 8, 7]
// 최소 3개
List<Integer> bottom3 = list.stream()
.sorted()
.limit(3)
.collect(Collectors.toList()); // [1, 2, 3]
7. 조건별 필터링 및 그룹화
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Map<String, List<Integer>> categorized = numbers.stream()
.collect(Collectors.groupingBy(n -> {
if (n % 3 == 0) return "divisible by 3";
if (n % 2 == 0) return "even";
return "odd";
}));
8. 범위 합계
// 1부터 n까지의 합
int n = 100;
int sum = IntStream.rangeClosed(1, n).sum(); // 5050
// 조건부 합계
int evenSum = IntStream.rangeClosed(1, 100)
.filter(i -> i % 2 == 0)
.sum(); // 2550
9. 2차원 배열 처리
int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// 평탄화
int[] flat = Arrays.stream(matrix)
.flatMapToInt(Arrays::stream)
.toArray(); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 합계
int sum = Arrays.stream(matrix)
.flatMapToInt(Arrays::stream)
.sum(); // 45
10. 조합 생성 (간단한 경우)
List<Integer> list = Arrays.asList(1, 2, 3);
// 모든 쌍 생성
List<int[]> pairs = list.stream()
.flatMap(i -> list.stream()
.filter(j -> i < j)
.map(j -> new int[]{i, j}))
.collect(Collectors.toList());
// [[1,2], [1,3], [2,3]]
11. Map의 값으로 정렬
Map<String, Integer> map = new HashMap<>();
map.put("apple", 5);
map.put("banana", 2);
map.put("cherry", 8);
// 값으로 내림차순 정렬
List<Map.Entry<String, Integer>> sorted = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.collect(Collectors.toList());
// [cherry=8, apple=5, banana=2]
// LinkedHashMap으로 (순서 유지)
Map<String, Integer> sortedMap = map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
12. 문자열 처리
String text = "Hello World";
// 문자별 빈도
Map<Character, Long> charFreq = text.chars()
.mapToObj(c -> (char)c)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// 단어별 빈도
String sentence = "apple banana apple cherry banana apple";
Map<String, Long> wordFreq = Arrays.stream(sentence.split(" "))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
7. 주의사항
7.1 스트림은 재사용 불가
Stream<Integer> stream = list.stream();
stream.forEach(System.out::println);
// stream.forEach(System.out::println); // IllegalStateException: stream has already been operated upon or closed
7.2 상태를 변경하지 말 것
// 잘못된 예
List<Integer> list = new ArrayList<>();
IntStream.range(0, 10).forEach(list::add); // 병렬 스트림에서 문제 발생 가능
// 올바른 예
List<Integer> list = IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList());
7.3 병렬 스트림 주의
// 순서가 중요한 경우 병렬 스트림 사용 주의
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.parallelStream().forEach(System.out::println); // 순서 보장 안됨
list.parallelStream().forEachOrdered(System.out::println); // 순서 보장
7.4 null 처리
// null이 포함된 리스트
List<String> list = Arrays.asList("a", null, "b", null, "c");
// NullPointerException 발생 가능
// list.stream().map(String::toUpperCase).collect(Collectors.toList());
// 올바른 처리
List<String> result = list.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.collect(Collectors.toList());
7.5 성능 고려
// 작은 데이터셋에서는 for문이 더 빠를 수 있음
List<Integer> small = Arrays.asList(1, 2, 3, 4, 5);
// Stream (오버헤드 존재)
int sum1 = small.stream().mapToInt(Integer::intValue).sum();
// 일반 for문 (더 빠름)
int sum2 = 0;
for (int num : small) {
sum2 += num;
}
7.6 Optional 처리
Optional<Integer> opt = list.stream().findFirst();
// 잘못된 예
if (opt.isPresent()) {
System.out.println(opt.get());
}
// 좋은 예
opt.ifPresent(System.out::println);
int value = opt.orElse(0);
int value2 = opt.orElseGet(() -> 0);
7.7 무한 스트림 주의
// limit 없으면 무한 루프
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
// infinite.forEach(System.out::println); // 무한 실행!
// 올바른 사용
infinite.limit(10).forEach(System.out::println);
'개발 지식 > JAVA' 카테고리의 다른 글
| Java Math 클래스와 기타 유틸리티 정리 (알고리즘 문제 풀이용) (0) | 2026.02.15 |
|---|---|
| Java 컬렉션 프레임워크 정리 (알고리즘 문제 풀이용) (0) | 2026.02.15 |
| Java 배열(Array)과 Arrays 클래스 정리 (알고리즘 문제 풀이용) (0) | 2026.02.15 |
| Java String, StringBuilder 메서드 정리 (알고리즘 문제 풀이용) (0) | 2026.02.15 |
| Java 래퍼 클래스 메서드 정리 (알고리즘 문제 풀이용) (0) | 2026.02.15 |