Stream

좋은 지적이에요.
**Obsidian 기준으로 “깨지지 않는 순수 Markdown”**으로 다시 정리해드릴게요.

아래 원칙을 지켰습니다.


2. 스트림 / 람다 / Collectors (최종코테 기준)


2-0. 최종코테에서의 스트림 사용 원칙

최종코테에서 스트림은 능력 과시용이 아니라 사고를 정리하는 도구다.

사용 기준:

피해야 할 기준:

읽어서 바로 이해되면 스트림, 아니면 for문이 정답이다.


2-1. Stream 생성 템플릿

컬렉션에서 생성

list.stream();
set.stream();
map.entrySet().stream();

배열에서 생성

String[] arr = {"a", "b", "c"};
Arrays.stream(arr);

숫자 스트림

IntStream.range(0, n);        // 0 ~ n-1
IntStream.rangeClosed(1, n);  // 1 ~ n

2-2. 중간 연산 템플릿

filter

List<String> result =
    names.stream()
         .filter(name -> name.length() >= 3)
         .toList();

가독성을 높이려면 boolean 메서드와 조합한다.

.filterisValid

map

List<Integer> lengths =
    names.stream()
         .maplength
         .toList();

flatMap

중첩 컬렉션을 펼칠 때만 사용한다.

List<String> tokens =
    lines.stream()
         .flatMap(line -> Arrays.stream(line.split(",")))
         .maptrim
         .toList();

distinct / sorted

List<String> uniqueSorted =
    names.stream()
         .distinct()
         .sorted()
         .toList();

peek (디버깅 전용)

list.stream()
    .peekprintln
    .toList();

실제 로직에 사용하지 않는다.


2-3. 종단 연산 템플릿

count

long count = list.stream().count();

anyMatch / allMatch / noneMatch

boolean anyInvalid =
    list.stream().anyMatch(x -> x < 0);

boolean allPositive =
    list.stream().allMatch(x -> x > 0);

max / min

Optional<Integer> max =
    scores.streamcompareTo;

int maxScore = max.orElseThrow();

sum

int sum =
    scores.stream()
          .mapToIntintValue
          .sum();

2-4. Collectors 핵심 템플릿

groupingBy (가장 중요)

값을 리스트로 그룹핑

Map<String, List<Player>> byTeam =
    players.stream()
           .collectteam);

값의 개수로 그룹핑

Map<String, Long> countByTeam =
    players.stream()
           .collect(Collectors.groupingBy(
               Player::team,
               Collectors.counting()
           ));

값의 합계로 그룹핑

Map<String, Integer> sumByTeam =
    players.stream()
           .collect(Collectors.groupingBy(
               Player::team,
               Collectors.summingIntscore
           ));

toMap (중복 키 주의)

Map<String, Integer> map =
    items.stream()
         .collect(Collectors.toMap(
             Item::name,
             Item::price
         ));

중복 키가 가능하면 반드시 merge 함수를 제공한다.

.collect(Collectors.toMap(
    Item::name,
    Item::price,
    Integer::sum
));

joining (출력용)

String result =
    names.stream()
         .collect(Collectors.joining(", "));

2-5. 일급 컬렉션과 스트림 결합 패턴

스트림은 외부에서 직접 쓰지 말고, 일급 컬렉션 내부로 이동시킨다.

나쁜 예

names.stream()
     .filter(...)
     .map(...)
     .toList();

좋은 예

public final class Names {
    private final List<String> values;

    public Names(List<String> values) {
        this.values = List.copyOf(values);
    }

    public Names longerThan(int length) {
        return new Names(
            values.stream()
                  .filter(name -> name.length() >= length)
                  .toList()
        );
    }

    public boolean anyStartsWith(String prefix) {
        return values.stream()
                     .anyMatch(name -> name.startsWith(prefix));
    }
}

호출부에서는 도메인 언어처럼 읽히게 된다.


2-6. 스트림과 for문 전환 기준

다음 상황에서는 for문이 더 낫다.

for (String s : list) {
    if (!isValid(s)) {
        throw new IllegalArgumentException();
    }
}

최종코테에서는 이 선택이 감점을 줄여준다.


2-7. 최종코테 스트림 체크리스트

반드시 익숙해져야 하는 것:

주의할 것:


다음 단계로 이어가면 가장 이상적인 흐름은 다음입니다.

원하시면 3번을 같은 Obsidian 친화 Markdown으로 바로 이어서 작성하겠습니다.