[JAVA] Stream에 대해 알아보자.

Lord·2024년 3월 12일

JAVA

목록 보기
3/7
post-thumbnail

Stream 의 등장

자바에서 Stream은 Java 8이 등장하면서 소개된 내용이다. 기존의 람다를 활용할 수 있는 기술의 하나로, 배열과 컬렉션을 함수형으로 처리하여 코드를 더욱 간결하게 작성할 수 있는 방법이다.

스트림의 동작 과정은 크게 3가지로 나누어 설명할 수 있다.

  1. 생성하기

  2. 가공하기

  3. 결과 생성하기

Stream 생성하기

보통은 배열과 컬렉션을 이용해서 스트림을 생성하지만 이외에도 다양한 방법의 스트림 생성과정이 있으니 주요 생성 방법을 한번 살펴보자.

1. 컬렉션으로부터 스트림 생성하기 - Collection.stream()

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        List<String> myList = Arrays.asList("apple", "banana", "orange");

        // 컬렉션으로부터 스트림 생성
        Stream<String> stream = myList.stream();

        // 스트림을 이용한 작업 수행
        stream.forEach(System.out::println);
    }
}

가장 많이 사용하는 방법 중 하나인 컬렉션으로 부터 스트림을 생성하는 방법이다. Arrays.asList()를 사용하여 리스트를 생성하고, 이 리스트로부터 stream() 메서드를 호출하여 스트림을 생성한다.

2. 배열로부터 스트림 생성하기 - Arrays.stream()

import java.util.stream.IntStream;

public class StreamCreationExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};

        // 배열로부터 스트림 생성
        IntStream stream = Arrays.stream(numbers);

        // 스트림을 이용한 작업 수행
        stream.forEach(System.out::println);
    }
}

배열을 사용하려면 정적인 메서드를 이용하면 된다. Arrays.stream() 메서드를 사용한다. 위의 예제는 int 배열로부터 IntStream을 생성하는 예제이다.

3. 값으로부터 스트림 생성하기 - Stream.of()

import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        // 값으로부터 스트림 생성
        Stream<String> stream = Stream.of("apple", "banana", "orange");

        // 스트림을 이용한 작업 수행
        stream.forEach(System.out::println);
    }
}

Stream.of() 메서드를 사용하여 여러 값을 가지는 스트림을 생성할 수 있다. 위의 예제에서는 문자열을 포함하는 스트림을 생성한다.

4. 무한 스트림 생성하기 - Stream.generate(), Stream.iterate()

import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        // 무한 스트림 생성: 랜덤한 정수
        Stream<Integer> randomStream = Stream.generate(() -> (int) (Math.random() * 100));

        // 무한 스트림 생성: 정수 시퀀스
        Stream<Integer> sequentialStream = Stream.iterate(0, n -> n + 2);

        // 각각의 스트림을 제한된 크기로 출력
        randomStream.limit(5).forEach(System.out::println);
        sequentialStream.limit(5).forEach(System.out::println);
    }
}

Stream.generate() 메서드는 Supplier 함수를 통해 무한한 요소를 생성하고, Stream.iterate() 메서드는 초기 값과 UnaryOperator 함수를 통해 무한한 시퀀스를 생성한다. 위의 예제는 각각 무한 스트림을 생성하고, limit() 메서드를 사용하여 처음 몇 개의 요소만 출력하는 예제이다.

Stream 가공하기

자바 스트림에서 가공이란 스트림의 요소들을 변환하거나, 걸러내거나, 정렬하는 등의 작업을 의미한다. 스트림은 중간 연산(intermediate operations)을 통해 이러한 가공 작업을 수행하며, 이러한 작업은 필요에 따라 연결하여 연쇄적으로 사용할 수 있다. 가공된 결과는 종단 연산(terminal operation)을 통해 최종적으로 수집하거나 처리된다.

연산설명예제
매핑(Mapping)각 요소를 다른 요소로 변환한다.map, flatMap
필터링(Filtering)주어진 조건에 맞는 요소들만을 걸러낸다.filter, distinct
정렬(Sorting)요소들을 정렬한다.sorted
제한(Limiting)요소의 개수를 제한한다.limit, skip
결합(Combining)두 개의 스트림을 결합한다.concat
기타(Miscellaneous)각 요소를 소비하면서 부가적인 작업을 수행한다.peek

그럼 각 내용에 대한 예제를 살펴보자.

1. 매핑(Mapping)

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MappingExample {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("apple", "banana", "orange");

        // 각 문자열을 대문자로 변환하여 새로운 리스트 생성
        List<String> uppercaseStrings = strings.stream()
                                                .map(String::toUpperCase)
                                                .collect(Collectors.toList());

        System.out.println(uppercaseStrings); // 출력: [APPLE, BANANA, ORANGE]
    }
}

매핑 연산을 통해 리스트에 있는 각 문자들을 대문자로 바꾸는 예제이다.

2. 필터링(Filtering)

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FilteringExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 짝수만을 걸러내는 필터링 작업
        List<Integer> evenNumbers = numbers.stream()
                                            .filter(n -> n % 2 == 0)
                                            .collect(Collectors.toList());

        System.out.println(evenNumbers); // 출력: [2, 4, 6, 8, 10]
    }
}

필터링연산을 통해 리스트 안에 있는 값들 중에서 짝수만을 걸러서 새로운 리스트에 추가하는 예제이다.

3. 정렬(Sorting)

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SortingExample {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("apple", "banana", "orange", "kiwi");

        // 문자열의 길이 순으로 정렬
        List<String> sortedStrings = strings.stream()
                                            .sorted((s1, s2) -> s1.length() - s2.length())
                                            .collect(Collectors.toList());

        System.out.println(sortedStrings); // 출력: [kiwi, apple, banana, orange]
    }
}

정렬 연산을 통해 문자열의 길이가 긴 순서대로 새로운 리스트에 추가하는 예제이다.

4. 제한(Limiting)

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class LimitingExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 처음 3개의 요소만 가져오기
        List<Integer> limitedNumbers = numbers.stream()
                                              .limit(3)
                                              .collect(Collectors.toList());

        System.out.println(limitedNumbers); // 출력: [1, 2, 3]
    }
}

제한 연산을 통해 앞의 3개의 숫자만 리스트에서 가져오는 예제이다.

5. 결합(Combining)

import java.util.stream.Stream;
import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;

public class CombiningExample {
    public static void main(String[] args) {
        List<Integer> numbers1 = Arrays.asList(1, 2, 3);
        List<Integer> numbers2 = Arrays.asList(4, 5, 6);

        // 두 리스트를 하나의 스트림으로 결합하여 새로운 리스트 생성
        List<Integer> combinedList = Stream.concat(numbers1.stream(), numbers2.stream())
                                            .collect(Collectors.toList());

        System.out.println(combinedList); // 출력: [1, 2, 3, 4, 5, 6]
    }
}

결합 연산을 통해 두 개의 다른 리스트를 하나의 스트림으로 결합하여 새로운 리스트를 생성하는 예제이다.

6. 기타(Miscellaneous)

import java.util.Arrays;
import java.util.List;

public class MiscellaneousExample {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("apple", "banana", "orange");

        // 각 요소를 출력하면서 길이도 출력
        strings.stream()
                .peek(s -> System.out.println("Length of " + s + ": " + s.length()))
                .forEach(System.out::println);
    }
}

peek() 연산을 통해 문자열과 문자열의 길이를 동시에 출력하도록 하는 예제이다.

Stream 결과 생성하기

위의 많은 예제에서 확인할 수 있는데, 스트림을 생성하고 가공하는 단계만 가지고 원하는 결과를 얻을 수 없다. 자신이 원하는 결과로 출력하기 위해서는 결과 생성 단계가 필수적으로 있어야하고 그래야 비로소 스트림에 대해 다룰 수 있다고 본다.

이러한 연산을 자바 스트림 최종 연산 단계라고 하며, 스트림의 최종 처리를 수행하고, 결과를 생성하거나 반환하는 연산이다. 최종 연산은 스트림의 각 요소를 처리하는 동안 수행되며, 스트림을 닫고 더 이상의 연산을 수행할 수 없게 한다.

종단 연산설명예제
forEach각 요소에 대해 지정된 동작을 수행한다.forEach(System.out::println)
collect스트림의 요소들을 수집하여 새로운 컬렉션 또는 결과를 생성한다.collect(Collectors.toList())
reduce스트림의 요소들을 결합하거나 집계하여 최종 결과를 생성한다.reduce(0, Integer::sum)
count스트림의 요소의 개수를 반환한다.count()
findAny스트림의 요소 중 일치하는 임의의 요소를 반환한다.findAny()
findFirst스트림의 요소 중 일치하는 첫 번째 요소를 반환한다.findFirst()
allMatch스트림의 요소들이 주어진 조건에 모두 맞는지 확인한다.allMatch(predicate)
anyMatch스트림의 요소들 중 하나라도 주어진 조건에 맞는지 확인한다.anyMatch(predicate)
noneMatch스트림의 요소들이 주어진 조건에 하나도 맞지 않는지 확인한다.noneMatch(predicate)
min스트림의 요소 중 최솟값을 반환한다.min(comparator)
max스트림의 요소 중 최댓값을 반환한다.max(comparator)
toArray스트림의 요소를 배열로 변환한다.toArray()

그럼 각 연산에 대한 예제를 살펴보자.

1. forEach

import java.util.Arrays;
import java.util.List;

public class ForEachExample {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("apple", "banana", "orange");

        // 각 요소를 출력
        fruits.stream().forEach(System.out::println);
    }
}

위의 예제에서 forEach 메서드는 각 요소를 출력한다. System.out::println은 람다 표현식을 사용하여 각 요소를 출력하는 메서드 참조방식이다.

2. collect

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CollectExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        // 짝수만을 수집하여 리스트 생성
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());

        System.out.println(evenNumbers); // 출력: [2, 4, 6]
    }
}

위의 예제에서 filter를 사용하여 짝수만을 걸러내고, collect를 사용하여 이를 리스트로 수집한다.

3. reduce

import java.util.Arrays;
import java.util.List;

public class ReduceExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 모든 요소를 합산
        int sum = numbers.stream().reduce(0, Integer::sum);

        System.out.println("Sum of numbers: " + sum); // 출력: Sum of numbers: 15
    }
}

위의 예제에서 reduce를 사용하여 모든 요소를 더하여 합산한다. 초기값으로 0을 사용하고, Integer::sum 메서드 참조를 사용하여 각 요소를 더한다. 이와 같이 reduce 연산은 스트림의 요소들을 조합하여 단일 결과를 생성하는 데 사용된다.

4. count

import java.util.Arrays;
import java.util.List;

public class CountExample {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("apple", "banana", "orange", "kiwi");

        // 요소의 개수 세기
        long count = fruits.stream().count();

        System.out.println("Number of elements: " + count); // 출력: Number of elements: 4
    }
}

count를 사용하여 문자열 리스트의 요소 개수를 세어 출력하고 있다.

5. findAny와 findFirst

findAny와 findFirst 연산은 스트림의 요소 중 일치하는 요소를 반환한다. 차이점은 병렬 스트림에서 동작할 때 발생한다.

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class FindExample {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("apple", "banana", "orange", "kiwi");

        // 첫 번째 요소 찾기
        Optional<String> firstElement = fruits.stream().findFirst();

        if (firstElement.isPresent()) {
            System.out.println("First element: " + firstElement.get());
        } else {
            System.out.println("List is empty.");
        }
    }
}

위의 예제에서 findFirst를 사용하여 문자열 리스트의 첫 번째 요소를 찾는다. 만약 리스트가 비어있다면 Optional 객체는 비어있게 된다.

6. allMatch, anyMatch, noneMatch

스트림의 요소들이 주어진 조건에 모두 맞는지(allMatch), 하나라도 맞는지(anyMatch), 하나도 맞지 않는지(noneMatch)를 확인한다.

import java.util.Arrays;
import java.util.List;

public class MatchExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);

        // 모든 요소가 짝수인지 확인
        boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0);

        System.out.println("All numbers are even: " + allEven); // 출력: All numbers are even: true
    }
}

위의 예제에서 allMatch를 사용하여 숫자 리스트의 모든 요소가 짝수인지 확인한다. 모든 요소가 짝수라면 true를 반환한다.

7. min

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class MinExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);

        // 최솟값 찾기
        Optional<Integer> minNumber = numbers.stream().min(Integer::compareTo);

        if (minNumber.isPresent()) {
            System.out.println("Minimum number: " + minNumber.get()); // 출력: Minimum number: 1
        } else {
            System.out.println("List is empty.");
        }
    }
}

위의 예제에서 min을 사용하여 숫자 리스트의 최솟값을 찾는다. 만약 리스트가 비어있다면 Optional 객체는 비어있는 객체이다.

8. max

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class MaxExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);

        // 최댓값 찾기
        Optional<Integer> maxNumber = numbers.stream().max(Integer::compareTo);

        if (maxNumber.isPresent()) {
            System.out.println("Maximum number: " + maxNumber.get()); // 출력: Maximum number: 9
        } else {
            System.out.println("List is empty.");
        }
    }
}

위의 예제에서 max를 사용하여 숫자 리스트의 최댓값을 찾는다. 마찬가지로 리스트가 비어있다면 Optional 객체는 비어있는 객체이다.

9. toArray

import java.util.Arrays;
import java.util.List;

public class ToArrayExample {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("apple", "banana", "orange");

        // 배열로 변환
        String[] fruitArray = fruits.stream().toArray(String[]::new);

        // 배열 출력
        System.out.println(Arrays.toString(fruitArray)); // 출력: [apple, banana, orange]
    }
}

위의 예제에서 toArray를 사용하여 문자열 리스트를 배열로 변환한다. String[]::new는 배열을 생성하는 메서드 참조방식이다.

마무리

이렇게 자바에서 스트림에 대한 기본적인 설명을 모두 마쳤다. 자바를 공부한다면 나중에 프레임워크인 스프링에서도 많이 사용하므로 꼭 알아두는 것이 좋다. 또한, 코딩테스트 준비를 할 때에도 코드가 간결하고 가독성이 좋아, 좋은 점수(?)를 받을 수 있는 요소가 될 수 있으니 꼭 숙지하도록 하자. 다음 포스팅에서는 자바 스트림에 대한 고급 내용을 간단하게 정리해 볼 예정이다.

profile
다재다능한 Backend 개발자에 도전하는 개발자

0개의 댓글