외부반복

반복문을 이용하여 각 요소를 순회한다.

List<String> names = Arrays.asList("kim", "rang", "dong", "jang");

for (String name : names) {
    System.out.println(name);
}

내부반복

무엇을 할지 정의하고 어떻게 수행될지에 대한 세부사항은 라이브러리에 의해 관리된다.

List<String> names = Arrays.asList("kim", "rang", "dong", "jang");

names.stream().forEach(this::print);

private void print(String name) { // 필수 X
    System.out.println(name);
}

스트림 요소 흐름

스트림 요소 흐름

스트림

Map/Filter/Reduce 세 가지 연산으로 축약됨

  • 맵(Map) : 데이터 변환
  • 필터(Filter) : 데이터 선택
  • 리듀스(Reduce) : 결과도출
public record Shape(int corners) implements Comparable<Shape> {
    
    public int cornersCount() {
        return corners();
    }

    public boolean hasCorners() {
        return corners() > 0;
    }

    public List<Shape> twice() {
        return List.of(this, this);
    }

    @Override
    public int compareTo(Shape o) {
        return Integer.compare(corners(), o.corners());
    }

    public static Shape circle() {
        return new Shape(0);
    }

    public static Shape triangle() {
        return new Shape(3);
    }

    public static Shape square() {
        return new Shape(4);
    }
}

스트림 중간 (요소선택)

filter

요소 중 filter에 참인 결과만 뽑는다.

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .filter(Shape::hasCorners)
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [Shape(4), Shape(3), Shape(3), Shape(4)]

dropWhile

요소 중 dropWhile 결과에 참일때까지 요소들을 버린다.

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .dropWhile(Shape::hasCorners)
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [Shape(0), Shape(4), Shape(0)]

takeWhile

요소 중 takeWhile 결과에 거짓일 때까지 요소를 선택한다.

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .takeWhile(Shape::hasCorners)
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [Shape(4), Shape(3), Shape(3)]

limit

요소 중 limit를 지정한 갯수까지만 선택한다.

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .limit(2)
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [Shape(4), Shape(3)]

skip

요소 중 skip를 지정한 갯수를 건너뛴다.

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .skip(2)
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [Shape(3), Shape(0), Shape(4), Shape(0)]

distinct

요소 중 중복된 것을 제거해줌

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .distinct()
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [Shape(4), Shape(3), Shape(0)]

sorted

Comparable을 상속(=implements)받은 경우 정렬 가능

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .sorted()
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [Shape(0), Shape(0), Shape(3), Shape(3), Shape(4), Shape(4)]

요소 매핑

map

요소를 새로운 요소로 반환하는 함수

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(4), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .map(Shape::cornersCount)
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [4, 3, 3, 0, 4, 0]

flatMap

Optional과 같은 컨테이너 형태의 요소를 펼쳐서 새로운 다중 요소를 포함하는 새로운 스트림으로 만듬

List<Shape> shapes = Arrays.asList(new Shape(4), new Shape(3), new Shape(0));


List<Shape> filterShapes = shapes.stream()
                                 .map(Shape::twice)
                                 .flatMap(List::stream)
                                 .collect(Collectors.toList());


System.out.println(filterShapes.toString()); // [new Shape(4), new Shape(4), new Shape(3), new Shape(3), new Shape(0), new Shape(0)]

Peek

중간결과를 디버깅하기 위해 사용

List<Shape> result = Stream.of(Shape.square(), Shape.triangle(), Shape.circle())
                           .map(Shape::twice)
                           .flatMap(List::stream)
                           .peek(shape -> System.out.println("모양: " + shape))
                           .filter(shape -> shape.corners() < 4)
                           .collect(Collectors.toList());

// 모양: Shape[corners=4]
// 모양: Shape[corners=4]
// 모양: Shape[corners=3]
// 모양: Shape[corners=3]
// 모양: Shape[corners=0]
// 모양: Shape[corners=0]

스트림 종료

네 가지 그룹으로 나뉨

  1. 축소
  2. 집계
  3. 찾기 및 매칭시키기
  4. 소비

요소 축소 (=fold 연산)

누적 연산자를 반복적으로 적용하여 스트림의 요소들을 하나의 결과값으로 만듬

reduce

identify : 시드값

T reduce(T identify, BinaryOperator<T> accumulator)

U reduce(U identify, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)

Optional<T> reduce(BinaryOperator<T> accumulator)

// 0부터 누적
int total = Stream.of(1, 2, 3, 4, 5, 6)
                .reduce(0, Integer::sum); // 21

int mapReduce = Stream.of("apple", "orange")
                      .mapToInt(String::length)
                      .reduce(0, (acc, length) -> acc + length); // 17


int reduceOnly = Stream.of("apple", "orange", "banana")
                       .reduce(0, (acc, str) -> acc + str.length(), Integer::sum); // 17

요소 집계

컬렉터를 활용한 요소 집계

java.util.Collection

toCollection(Supplier<C> collectionFactory)

toList(), toSet()

toUnmodifiabelList(), toUnmodifiableSet() : 자바 10+ 직렬화 가능성, 스레드 안정성, 가변성 등 고려

java.util.Map

toMap(), toCurrentMap()

toUnmodifiableMap() : 자바 10+ 직렬화 가능성, 스레드 안정성, 가변성 등 고려

찾기 및 매칭시키기

함수 설명
Optional<T> findFirst() 첫 번째로 만나는 스트림의 요소를 반환함, 스트림이 비어있다면 빈 Optional<T>를 반환
Optional<T> findAny() 스트림 내의 임의의 요소를 반환, 스트림이 비어있다면 빈 Optional<T>를 반환
boolean anyMatch(Predicate<? super T> predicate) predicate와 일치하는 요소가 스트림에 하나라도 존재한다면 true 반환
boolean allMatch(Predicate<? super T> predicate) 스트림의 모든 요소가 predicate와 일치하면 true 반환
boolean noneMatch(Predicate<? super T> predicate) predicate와 일치하는 요소가 스트림에 없으면 true 반환

요소 소비

함수 설명
void forEach(Consumer<? super T> action) 각 요소마다 주어진 동작을 수행함
void forEachOrdered(Consumer<? super T>) 스트림이 ORDERED 상태라면 만나게 되는 순서에 따라 각 요소에 대한 action이 실행됨

연산비용

파이프라인을 통과하는 요소가 적을수록 성능은 향상된다.

filter() 같은 연산을 먼저 하자

  1. map() 연산 : 5번실행
  2. sorted() 연산 : 8번실행
  3. filter() 연산 : 5번실행
  4. forEach() 연산 : 2번실행
  • 결과 : 총 20번의 연산 실행
Stream.of("ananas", "oranges", "apple", "pear", "banana")
    .map(String::toUpperCase)
    .sorted()
    .filter(str -> str.startsWith("A"))
    .forEach(System.out::println);
  1. filter() 연산 : 5번실행
  2. map() 연산 : 2번실행
  3. sorted() 연산 : 1번 실행
  4. forEach() 연산 : 2번 실행
  • 결과 : 총 10번의 연산 실행
Stream.of("ananas", "oranges", "apple", "pear", "banana")
    .filter(str -> str.startsWith("a"))
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);

쇼트 서킷 (short-circuit)

스트림을 조기에 중단하는 것

스트림이 모든 요소를 순회하지 않아도 목적을 달성할 수 있는 연산

논쇼트 서킷 (non-short-circuit)

int result = Stream.of("apple", "orange", "banana", "melon")
                .peek(str -> Sytem.out.println("peek 1: " + str))
                .map(str -> {
                    System.out.println("map: " + str);
                    return str.toUpperCase();
                })
                .peek(str -> System.out.println("peek 2: " + str))
                .count();
// 출력결과 없음
int result = Stream.of("apple", "orange", "banana", "melon")
                .filter(str -> str.contains("e"))
                .peek(str -> Sytem.out.println("peek 1: " + str))
                .map(str -> {
                    System.out.println("map: " + str);
                    return str.toUpperCase();
                })
                .peek(str -> System.out.println("peek 2: " + str))
                .count();
/**
 * 출력
 * peek 1: apple
 * map: apple
 * peek 2: APPLE
 * peek 1: orange
 * map: orange
 * peek 2: ORANGE
 * peek 1: melon
 * map: melon
 * peek 2: MELON
 */

댓글남기기