# Table of Contents

# 스트림

Java 8에서 추가된 스트림(Stream)을 사용하면 Collection을 더욱 쉽게 순회, 필터링, 변환할 수 있다. 특히 람다식과 함께 사용하면 코드가 더욱 간결해진다.

간단한 예제를 살펴보면서 스트림이 어떤 것인지 알아보자. 1에서 6까지의 숫자가 List에 들어있다.

List<Integer> numbers = new ArrayList<Integer>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
numbers.add(6);

이제 반복문을 사용하여 짝수만 추출해보자.

List<Integer> evenNumbers = new ArrayList<Integer>();

for (int i=0; i<numbers.size(); i++) {
    if (numbers.get(i) % 2 == 0) {
        evenNumbers.add(numbers.get(i));
    }
}

System.out.println(evenNumbers.toString()); // [2, 4, 6]

스트림(Stream)을 사용하면 더욱 쉽게 짝수를 추출할 수 있다.

List<Integer> evenNumbers = numbers.stream()
        .filter(value -> value%2 == 0)
        .collect(Collectors.toList());

System.out.println(evenNumbers.toString());     // [2, 4, 6]

# 스트림 생성하기

다양한 방법으로 스트림을 생성할 수 있다.

# Stream.builder()

Builder를 사용하면 스트림에 값을 직접 넣을 수 있다.

Stream<Integer> stream = Stream.<Integer>builder()
    .add(1)
    .add(2)
    .add(3)
    .add(4)
    .build();

stream
    .forEach(System.out::println);
1
2
3
4

# Stream.generate()

Stream.generate()을 사용하면 람다식으로 스트림을 생성할 수 있다. generate()만 사용하면 요소를 무한히 발행하며, limit()메소드로 발행되는 요소의 수를 제한할 수 있다.

Stream<Integer> stream = Stream.generate(() -> 1).limit(5);

stream
    .forEach(System.out::println);
1
1
1
1
1

# Stream.iterate()

Stream.iterate()은 초기값과 람다식을 이용하여 스트림을 생성한다.

Stream<Integer> stream = Stream.iterate(100, x -> x+1).limit(5);

stream
    .forEach(System.out::println);
100
101
102
103
104

# Stream.of()

Stream.of()은 매개변수로 전달된 요소들로 스트림을 생성한다.

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

stream
    .forEach(System.out::println);
1
2
3
4
5

# Stream.empty()

Stream.empty()을 사용하면 빈 스트림을 생성할 수 있다.

Stream stream = Stream.empty();

stream
    .forEach(System.out::println);

# Arrays.stream()

Arrays.stream()을 사용하면 배열로 스트림을 만들 수 있다.

Integer[] array  = {1, 2, 3, 4, 5};
Stream<Integer> stream = Arrays.stream(array);

stream
    .forEach(System.out::println);
1
2
3
4
5

# List.stream()

List로 스트림을 생성할 수 있다.

List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);

Stream<Integer> stream = list.stream();

stream
    .forEach(System.out::println);
1
2
3
4

# Set.stream()

Set로 스트림을 생성할 수 있다.

Set<Integer> set = new HashSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
set.add(3);

Stream<Integer> stream = set.stream();

stream
    .forEach(System.out::println);
1
2
3

# 원시 타입 스트림

Wrapper 클래스와 제너릭을 사용하면 원시 타입의 스트림을 생성할 수 있다.

Stream<Integer> stream = Stream.<Integer>of(1, 2, 3, 4, 5);

stream
    .forEach(System.out::println);
1
2
3
4
5

그러나 Wrapper 클래스를 사용하면 빈번한 오토박싱, 언박싱이 발생한다. 따라서 Stream API는 원시 타입을 위한 스트림을 제공한다.

  • IntStream
  • LongStream
  • DoubleStream

원시 타입의 스트림은 다음과 같이 사용한다.

IntStream stream = IntStream.of(1, 2, 3, 4, 5);

stream
    .forEach(System.out::println);
1
2
3
4
5

# 스트림 조작하기

# Stream.concat()

Stream.concat()을 사용하면 두 스트림을 합쳐 새로운 스트림을 만들 수 있다.

Stream<Integer> stream1 = Stream.<Integer>of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = Stream.<Integer>of(6, 7, 8, 9, 10);

Stream<Integer> stream = Stream.concat(stream1, stream2);

stream
    .forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10

# Stream.filter()

Stream.filter()을 사용하면 요소를 필터링할 수 있다. 아래 예제는 짝수만 필터링한다.

Stream<Integer> stream = Stream.<Integer>of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .filter(value -> value%2 == 0);

stream
        .forEach(System.out::println);
2
4
6
8
10

# Stream.map()

Stream.map()을 사용하면 요소들을 다른 값들로 매핑할 수 있다.

Stream<String> stream = Stream.of("Paul", "Monica", "Rachel", "Chandler")
    .map(value -> value.toUpperCase());

stream
    .forEach(System.out::println);
PAUL
MONICA
RACHEL
CHANDLER

# Stream.flatMap()

Stream.flatMap()을 사용하면 요소를 다른 스트림으로 만들 수 있다.

Stream<Integer> stream = Stream.of(
    new Student("Joey", 80, 90, 70),
    new Student("Monica", 75, 100, 90),
    new Student("Ross", 70, 80, 100)
).flatMap(student -> Stream.<Integer>of(student.getMath(), student.getEnglish(), student.getScience()));

stream
    .forEach(System.out::println);
80
90
70
75
100
90
70
80
100

# Stream.sorted()

Stream.sorted()을 사용하면 요소들을 정렬할 수 있다. 기본값은 오름차순이다.

Stream<Integer> stream = Stream.of(2, 3, 1, 5, 6, 4)
    .sorted();

stream
    .forEach(System.out::println);
1
2
3
4
5
6

역순으로 정렬할 수도 있다.

Stream<Integer> stream = Stream.of(2, 3, 1, 5, 6, 4)
    .sorted(Collections.reverseOrder());

stream
    .forEach(System.out::println);
6
5
4
3
2
1

Comparator 또는 Comparable과 함께 사용할 수도 있다.

Stream<Person> stream = Stream.of(new Person("Paul", 35), new Person("Smith", 25), new Person("Jane", 15))
    .sorted(new Comparator<Person>() {
        @Override
        public int compare(Person p1, Person p2) {
            return p1.getAge() - p2.getAge();
        }
    });

stream
    .forEach(System.out::println);

# 스트림으로 결과 만들기

# Stream.foreach()

Stream.foreach()을 사용하면 모든 요소들을 순회할 수 있다.

Stream<Integer> stream = Stream.of(2, 3, 1, 5, 6, 4);

stream
    .forEach(System.out::println);
2
3
1
5
6
4

# Stream.count()

요소의 개수를 구할 수 있다.

Stream<Integer> stream = Stream.of(2, 3, 1, 5, 6, 4);

long count = stream.count();    // 6

# Stream.sum()

요소의 합을 구할 수 있다.

IntStream stream = IntStream.of(2, 3, 1, 5, 6, 4);

long count = stream.sum();    // 21

# Stream.min()

최소값을 찾을 수 있다.

IntStream stream = IntStream.of(2, 3, 1, 5, 6, 4);

OptionalInt min = stream.min();    

min.ifPresent(System.out::println);     // 1

# Stream.max()

최대값을 찾을 수 있다.

IntStream stream = IntStream.of(2, 3, 1, 5, 6, 4);

OptionalInt max = stream.max();

max.ifPresent(System.out::println);     // 6

# Stream.average()

평균값을 찾을 수 있다.

IntStream stream = IntStream.of(2, 3, 1, 5, 6, 4);

OptionalDouble average = stream.average();

average.ifPresent(System.out::println);     // 3.5

# Stream.reduce()

Stream.reduce()는 각 요소에 람다식을 실행하여 단 1개의 결과를 반환한다.

IntStream stream = IntStream.of(2, 3, 1, 5, 6, 4);

OptionalInt sum = stream.reduce((acc, next) -> acc + next);

sum.ifPresent(System.out::println);     // 21

# collect()와 Collectors

collect()메소드는 데이터의 중간 처리 후 마지막에 원하는 형태로 변환해준다. 이 메소드는 보통 Collectors클래스와 함께 사용하며, 다음과 같은 기능을 제공한다.

  • Stream 요소들을 List, Set, Map 등으로 변환
  • Stream 요소들을 결합
  • Stream 요소들의 통계값 (최대값, 최소값, 평균값 등..)
  • Stream 요소들의 그룹핑, 분할, ..

# Collectors.toList()

스트림을 List로 변환한다.

List<Integer> list = Stream.<Integer>of(2, 3, 1, 5, 6, 4)
    .collect(Collectors.toList());

# Collectors.toSet()

스트림을 Set으로 변환한다.

Set<Integer> set = Stream.<Integer>of(2, 3, 1, 5, 6, 4)
    .collect(Collectors.toSet());

set.forEach(System.out::println);

# Collectors.joining()

스트림을 하나의 문자열로 결합할 수 있다.

String str = Stream.<String>of("AAA", "BBB", "CCC", "DDD")
    .collect(Collectors.joining());

System.out.println(str);    // AAABBBCCCDDD
String str = Stream.<String>of("AAA", "BBB", "CCC", "DDD")
    .collect(Collectors.joining("+", "<", ">"));

System.out.println(str);    // <AAA+BBB+CCC+DDD>

# Collectors.count()

요소의 개수를 반환한다.

Long count = Stream.<Integer>of(2, 3, 1, 5, 6, 4)
    .collect(Collectors.counting());