스트림은 무엇이며, 어떻게 사용할까?
스트림 (Stream)
--
스트림은
다양한 "데이터 소스"를 표준화된 방법으로 다루기 위한 방법이며,
효율적으로 처리하고 변환하기 위해 사용된다.
데이터 소스는 컬렉션(List, Set, Map 등)이나 배열 같이 여러 데이터들을 관리하는 것을 의미
그래서 컬렉션 또는 배열 등 여러 데이터를 관리하는 기능에서는 Stream을 지원하여
Stream을 사용하면 모두 동일한 방법으로 작업을 처리할 수 있게 된다.
스트림에서 제공하는 연산 2가지
- 중간 연산 : 연산 결과가 스트림인 연산 (즉, 계속 연산을 이어서 진행 가능)
- 최종 연산 : 연산 결과가 스트림이 아닌 최종 값인 연산 (즉, 더 이상 연산 불가능)
- 다양한 데이터 소스를 가지고 Stream을 만들고 나면 이후의 작업은 모두 동일하다.
- 최종 연산을 하기 전까지는 얼마든지 중간 연산을 할 수 있다.
- 최종 연산을 하는 순간 작업은 끝나게 되어 더이상 작업을 수행하지 못한다.
각 데이터 소스들을 Stream으로 변환하는 방법
// 컬렉션 (stream()을 붙여 호출하면 변환)
Stream<Integer> intStream = list.stream();
// 배열 (Stream.of(배열)을 호출하면 변환)
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"});
// 람다식
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2);
// 람다식
Stream<Double> randomStream = Stream.generate(Math::random);
// 난수 스트림(크기가 5)
IntStream intStream = new Random().ints(5);
컬렉션 안에는 Stream()메서드가 존재하여 바로 stream() 메서드를 호출하여 변환이 가능하다.
스트림 특징
- 스트림은 데이터 소스(원본)로 부터 데이터를 읽기만 가능 (즉, 원본에 영향 없음)
- 스트림은 Iterator처럼 일회용 (즉, 필요하면 다시 스트림을 생성해서 구해야 함)
- 중간 연산은 최종 연산이 호출될 때까지 실행되지 않는다. (이를 "지연 연산"이라고 부른다.)
- 연산할 때 내부적으로 반복하여 처리된다. (그래서 성능은 효율적이지 않지만 코드가 간결해진다.)
- 기본형을 사용할 때 오토박싱, 언박싱의 비효율을 제거하기 위해 기본형 스트림 제공
--
[1단계] 스트림 생성
--
컬렉션 => 스트림
Collection 인터페이스에서 제공하는 stream() 메서드를 사용하여 스트림으로 변환 가능
Stream<E> stream()
List<Integer> list = Arrays.asList(1,2,3,4,5);
// 컬렉션 list를 스트림으로 변환
Stream<Integer> intStream = list.stream();
배열 => 스트림
Stream 인터페이스에서 제공하는 of() 메서드를 사용하여 배열 형식을 스트림으로 변환 가능
또는 Arrays 클래스에서 제공하는 stream() 메서드를 사용하여 스트림으로 변환 가능
// Stream 클래스에서 제공하는 of() 메서드
Stream<T> Stream.of(T... values) // 가변 인자
Stream<T> Stream.of(T[]) // 배열
// Arrays 클래스에 있는 stream() 메서드
Stream<T> Arrays.stream(T[]) // 배열
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive) // 배열의 특정 범위
- array : 배열
- startInclusive : 범위 첫 인덱스
- endExclusive : 범위 마지막 인덱스 (마지막 인덱스는 범위에 포함 X, 마지막 인덱스 이전까지만 포함)
Stream<String> strStream = Stream.of("a", "b", "c");
Stream<String> strStream = Stream.of(new String[] {"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[] {"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[] {"a", "b", "c"}, 0, 3);
기본형 배열 => 스트림
타입에 맞는 기본형 스트림 사용 (IntStream, DoubleStream 등)
// int형 예시
// IntStream에서 제공하는 메서드 사용
IntStream IntStream.of(int... values)
IntStream IntStream.of(int[])
// Arrays에서 제공하는 메서드 사용
IntStream Arrays.stream(int[])
IntStream Arrays.stream(int[] array, int startInclusive, int endExclusive)
난수(Random) => 스트림
스트림은 "유한 스트림", "무한 스트림"이 존재한다.
- 유한 스트림 : 값의 범위가 정해져 있음
- 무한 스트림 : 값의 범위가 무한정 있음 ( 계속 값이 찍어서 나옴 )
무한 스트림인 경우
limit()라는 중간 연산을 사용하여 사용할 범위를 정해줘야 한다.
그렇지 않으면 값이 계속 무한으로 출력되므로 안된다.
Random 클래스에서 제공하는 메서드
// 무한 스트림
IntStream ints(int begin, int end)
LongSteram longs(long begin, long end)
DoubleStream doubles(double begin, double end)
// 유한 스트림
IntStream ints(long streamSize, int begin, int end)
LongSteram longs(long streamSize, long begin, long end)
DoubleStream doubles(long streamSize, double begin, double end)
- streamSize : 스트림 크기
- begin, end : 출력될 난수 범위
만약 출력될 값의 범위를 지정하지 않으면 지정되는 기본 범위 값
Integer.MIN_VALUE <= ints() <= Integer.MAX_VALUE
Long.MIN_VALUE <= longs() <= Long.MAX_VALUE
0.0 <= doubles() < 1.0
IntStream intStream = new Random().ints(); // 무한 스트림 (int형의 난수)
intStream.limit(5).forEach(System.out::println); // limit(5)로 크기가 5로 잘라냄
// 아니면 처음부터 크기를 지정해줄 수 있다.
IntStream intStream = new Random().ints(5); // 크기가 5인 난수 스트림을 반환
특정 범위의 정수 => 스트림
IntStream IntStream.range(int begin, int end) // end 미포함 (end 이전까지만 포함)
IntStream IntStream.rangeClosed(int begin, int end) // end 포함
IntStream intStream = IntStream.range(1, 5);
// 1, 2, 3, 4
IntStream intStream = IntStream.rangeClosed(1, 5);
// 1, 2, 3, 4 ,5
--
[2단계] 중간 연산
--
distinct()
스트림에서 중복된 값을 제거한다.
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4);
numbers.stream()
.distinct()
.forEach(System.out::println);
// 출력: 1, 2, 3, 4
forEach(System.out::println)는 최종 연산으로 중간 연산을 모두 수행한 결과를 출력하라는 의미다.
filter(Predicate<T> predicate)
매개변수에 람다식으로된 조건식을 넣어
해당 조건에 일치하지 않는 요소들을 제외한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
// 출력: Alice
limit(long maxSize)
첫 번째 요소부터 매개변수에 지정한 위치까지의 요소만 추출한다.
Stream<Integer> numbers = Stream.iterate(1, n -> n + 1); // 무한 스트림 (1, 2, 3, ...)
numbers.limit(5)
.forEach(System.out::println);
// 출력: 1, 2, 3, 4, 5
skip(long n)
limit()과 반대로 매개변수에 지정한 위치부터 요소를 추출한다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.skip(2)
.forEach(System.out::println);
// 출력: 3, 4, 5
peek(Consumer<T> action)
최종연산 forEach()와 비슷한 동작으로
해당 스트림의 요소들을 출력한다.
단 forEach()와는 다르게 중간 연산이므로 스트림이 닫히지 않고 계속 중간 연산을 이어서 할 수 있다.
즉, 중간에 작업이 잘 진행되고 있나 검사하는 용도로 사용된다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.peek(System.out::println) // 출력: Alice, Bob, Charlie
.map(String::toUpperCase)
.forEach(System.out::println);
// 출력: ALICE, BOB, CHARLIE
sorted(),
sorted(Compatator<T> compatator)
스트림의 요소를 정의한 기준으로 정렬한다.
만약 요소를 정의하지 않으면 해당 스트림 요소가 가지는 기본 정렬대로 정렬된다.
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
numbers.stream()
.sorted()
.forEach(System.out::println);
// 출력: 1, 1, 3, 4, 5, 9
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
// 출력: Charlie, Bob, Alice
map(Function<T, R> mapper),
mapToDouble(ToDoubleFunction<T> mapper),
mapToInt(ToIntFunction<T> mapper),
mapToLong(ToLongFunction<T> mapper)
스트림의 각 요소를 람다식으로 정의한 다른 형태로 변환한다.
만약 Double, Int, Long 값인 형태로 변환할 때에는 mapToDouble(), mapToInt(), mapToLong()를 사용한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.map(String::length) // 문자열을 문자크기로 변환
.forEach(System.out::println);
// 출력: 5, 3, 7
List<String> values = Arrays.asList("1.2", "3.4", "5.6");
values.stream()
.mapToDouble(Double::parseDouble)
.forEach(System.out::println);
// 출력: 1.2, 3.4, 5.6
FlatMap(Function<T, Stream<R>> mapper),
FlatMapToDuble(Function<T, DoubleStream> mapper),
FlatMaptToInt(Function<T, IntStream> mapper),
FlatMapTolong(Function<T, LongStream> mapper)
스트림의 각 요소를 담라식으로 정의한 다른 형태로 변환 후 다시 하나의 스트림으로 병합한다.
이 또한 Double, Int, Long 값인 형태로 변환할 때에는 위 3개를 사용한다.
List<List<String>> namesList = Arrays.asList(
Arrays.asList("Alice", "Bob"),
Arrays.asList("Charlie", "David")
);
namesList.stream()
.flatMap(List::stream)
.forEach(System.out::println);
// 출력: Alice, Bob, Charlie, David
--
[3단계] 최종 연산
--
void forEach(ConSumer<? super T> action)
void forEachOrdered(Consumer<? super T> action)
스트림의 각 요소에 대해 지정된 작업을 수행한다.
여기서 forEachOrdered()는 스트림의 순서에 따라 처리되는 것으로
병렬 스트림으로 처리할 때 순서에 맞게 처리하기 위해 사용된다. (순서유지 기능)
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(System.out::println);
// 출력: Alice, Bob, Charlie
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.parallelStream()
.forEachOrdered(System.out::println);
// 순서 보장: Alice, Bob, Charlie
long count()
스트림의 요소 수를 반환한다.
long count = Stream.of("A", "B", "C")
.count();
System.out.println(count);
// 출력: 3
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
지정한 비교 조건을 기준으로 최대 값 / 최소 값을 반환한다.
Optional<Integer> max = Stream.of(1, 2, 3, 4)
.max(Integer::compareTo);
max.ifPresent(System.out::println);
// 출력: 4
Optional<Integer> min = Stream.of(1, 2, 3, 4)
.min(Integer::compareTo);
min.ifPresent(System.out::println);
// 출력: 1
Optional<T> findAny()
Optional<T> findFirst()
스트림에서 요소 하나를 반환한다.
여기서 findAyn()는 임의의 요소 하나를 반환하고
findFirst()는 첫 번째 요소를 반환한다.
즉, findAny()는 "병렬 처리"에서 사용하고, findFirst()는 "직렬 처리"에서 사용된다.
Optional<String> any = Stream.of("A", "B", "C")
.findAny();
any.ifPresent(System.out::println);
// 출력: A (또는 B, C)
Optional<String> first = Stream.of("A", "B", "C")
.findFirst();
first.ifPresent(System.out::println);
// 출력: A
boolean allMatch(Predicate<T> p)
boolean anyMatch(Predicate<T> p)
boolean noneMatch(Predicate<T> p)
지정한 조건에 따라 true 또는 false를 반환
// 모든 요소가 조건에 만족하면 true 반환
boolean allEven = Stream.of(2, 4, 6)
.allMatch(n -> n % 2 == 0);
System.out.println(allEven);
// 출력: true
// 하나 이상의 요소가 조건을 만족하면 true 반환
boolean anyOdd = Stream.of(1, 2, 3)
.anyMatch(n -> n % 2 != 0);
System.out.println(anyOdd);
// 출력: true
// 모든 요소가 조건을 만족하지 않으면 true 반환
boolean noneNegative = Stream.of(1, 2, 3)
.noneMatch(n -> n < 0);
System.out.println(noneNegative);
// 출력: true
Object[] toArray()
A[] toArray(Infunction<A[]> generator)
스트림의 요소를 배열로 반환한다.
여기서 매개변수가 없는 toArray()는 객체 배열로 담아서 반환하고
매개변수로 특정 타입의 배열로 지정하여 반환할 수도 있다.
Object[] array = Stream.of("A", "B", "C")
.toArray();
System.out.println(Arrays.toString(array));
// 출력: [A, B, C]
String[] array = Stream.of("A", "B", "C")
.toArray(String[]::new);
System.out.println(Arrays.toString(array));
// 출력: [A, B, C]
Optional<T> reduce(BinaryOperator<T> accumlator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U, T, U> accumulator, BinaryOperator<U> cominer)
요소를 하나씩 줄여가면서 계산을 수행한다.
만약 sum을 한다면 요소 하나씩 꺼내서 덧셈을 수행하게 되는 것이다.
- identity : 초기값
- accumulator : 이전 연산결과와 스트림의 요소에 수행할 연산
- combiner : 병렬처리된 결과를 합치는 데 사용할 연산 (병렬 스트림)
// 스트림 요소를 하나씩 누적하여 하나의 값으로 계산한다.
Optional<Integer> sum = Stream.of(1, 2, 3)
.reduce(Integer::sum);
sum.ifPresent(System.out::println);
// 출력: 6
// 초기값을 제공하여 스트림 요소를 누적하여 계산한다.
int sum = Stream.of(1, 2, 3)
.reduce(0, Integer::sum);
System.out.println(sum);
// 출력: 6
// 병렬 스트림에서 누적 값을 계산한다.
int sum = Stream.of(1, 2, 3)
.parallel()
.reduce(0, (a, b) -> a + b, Integer::sum);
System.out.println(sum);
// 출력: 6
R collect(Collector<T, A, R> collector)
R collect(Supplier<R> supplier, BiConsumer<R, T> accumlator, BiConsumer<R, R> combiner)
스트림의 요소를 누적하여 그룹화하거나 컬렉션에 담아 반환한다.
// 스트림의 요소를 결과 컨테이너에 누적한다.
List<String> list = Stream.of("A", "B", "C")
.collect(Collectors.toList());
System.out.println(list);
// 출력: [A, B, C]
// 사용자가 정의한 수집기로 컬렉션 정의
List<String> list = Stream.of("A", "B", "C")
.collect(ArrayList::new, List::add, List::addAll);
System.out.println(list);
// 출력: [A, B, C]
--
'Language > Java' 카테고리의 다른 글
람다식 (+ 메서드 참조, 함수형 인터페이스) (0) | 2025.01.12 |
---|---|
스레드의 동기화 (0) | 2025.01.09 |
스레드의 상태 종류 & 제어 (0) | 2025.01.08 |
스레드 (Thread) (0) | 2024.12.29 |
제네릭 (Generics) (0) | 2024.12.28 |