스트림이란?
Java 8부터 추가된 스트림(Stream)은 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자다. 스트림을 활용해서 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현 가능하다.
- Java 6 이전 : iterator 객체 이용
- forEach 구문 이용
- 스트림 이용
스트림의 특징
- 스트림은 원본 데이터를 변경하지 않는다.
- 스트림은 일회성이다. 컬렉션의 요소를 모두 읽고 나면 닫혀서 다시 사용할 수 없다. 필요하다면 재생성해야 한다.
- 내부 반복자를 사용하므로 병렬 처리가 가능하다. 내부 반복자를 사용해서 얻는 이점은 컬렉션 내부에서 어떻게 요소를 반복시킬 것인가는 컬렉션에게 맡겨두고, 개발자는 요소 처리 코드에만 집중할 수 있다는 것이다.
스트림의 구조
1) 스트림 생성 : 스트림 인스턴스 생성
2) 스트림 데이터 가공 : 필터링 및 매핑 등을 통해 원하는 결과를 만들어가는 중간 작업
3) 결과 만들기 : 최종적으로 결과를 만들어내는 작업
스트림 생성
스트림을 이용하려면 스트림을 생성해야 한다.
스트림 생성 (배열)
String[] array = new String[]{"a", "b", "c", "d"};
Stream<String> stream1 = Arrays.stream(array);
Stream<String> stream2 = Arrays.stream(array, 1, 3); // 시작 인덱스 1, 종료 인덱스 3 -> ["b", "c"]
스트림 생성 (컬렉션)
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
스트림 생성 (컬렉션)
Stream<String> builderStream = Stream.<String>builder()
.add("Python")
.add("C")
.add("Java")
.build();
// ["Python", "C", "Java"]
스트림 생성 (generate)
generate 메서드를 이용하면 Supplier<T> 에 해당하는 람다로 값을 넣을 수 있다. Supplier<T> 는 인자는 없고 리턴값만 있는 함수형 인터페이스다. 람다에서 리턴하는 값이 들어간다.
public static<T> Stream<T> generate(Supplier<T> s) { ... }
이때 생성되는 스트림은 크기가 무한하기 때문에 limit 을 호출하여 특정 사이즈로 최대 크기를 제한해줘야 한다.
Stream<String> generatedStream = Stream.generate(() -> "hi").limit(5);
// ["hi", "hi", "hi", "hi", "hi"]
스트림 생성 (iterate)
iterate 메서드를 이용하면 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어갈 요소를 생성한다. 마찬가지로 생성되는 스트림의 크기가 무한하기 때문에 특정 사이즈로 제한해야 한다.
Stream<String> stream = Stream.iterate(30, n -> n + 2).limit(5);
// ["30", "32", "34", "36", "38"]
스트림 생성 (기본 타입)
range와 rangeClosed는 범위에 차이가 있다. range는 두번째 인자인 종료 지점이 포함되지 않는다.
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]
위 경우는 오토박싱이 수행되지 않는다.
제네릭을 이용한 클래스로 사용하려면 박싱을 해서 사용해야 한다.
Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();
스트림 생성 (두 개의 스트림 연결)
Stream<String> stream1 = Stream.of("Apple", "Banana", "Grape");
Stream<String> stream2 = Stream.of("Kim", "Lee", "Park");
Stream<String> stream3 = Stream.concat(stream1, stream2);
// ["Apple", "Banana", "Grape", "Kim", "Lee", "Park"]
이 외에도 빈 스트림, 문자열 스트림, 파일 스트림 등 다양한 형식의 만들 수 있다. 더보기
스트림 데이터 가공
데이터의 형변환, 필터링, 정렬 등 스트림에 대한 가공을 수행한다. 데이터를 가공해주는 메서드들은 가공된 결과를 생성해주는 스트림 객체를 리턴한다.
가공 (filter)
Stream<T> filter(Predicate<? super T> predicate);
filter 메서드는 boolean 값을 리턴하는 람다식을 넘겨주게 된다. 각 요소들에 대해 람다식을 적용해서 true가 리턴되는 데이터만 선별한다.
Stream<Integer> stream = IntStream.range(1, 10).boxed();
stream.filter(i -> ((i % 2) == 0))
.forEach(System.out::println);
가공 (map)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map은 스트림 내 요소들을 하나씩 특정 값으로 변환해준다. 이때 값을 변환하기 위한 람다를 인자로 받는다.
Stream<String> stream = Pattern.compile(", ").splitAsStream("Apple, Banana, Grape");
stream.map(String::toUpperCase)
.forEach(System.out::println);
가공 (flatMap)
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap 메서드의 인자로 받는 람다는 리턴 타입이 스트림이다. 즉, 새로운 스트림을 생성해서 리턴하는 람다를 인자로 넘겨야 한다. flatMap은 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어주는 역할을 하는데, 이러한 작업은 플랫트닝(flattening)이라고 한다.
// 중첩된 리스트
List<List<String>> list = Arrays.asList(Arrays.asList("a"), Arrays.asList("b")); // [["a"], ["b"]]
// 중첩 구조 제거
List<String> flatList = list.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()); // ["a", "b"]
가공 (sorted)
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
인자 없이 sorted 메서드를 호출할 때는 오름차순으로 정렬한다. 만약 정렬할 때 두 값을 비교하는 별도의 로직이 있다면, comparator를 sorted 메서드의 인자로 넘겨줄 수 있다.
List<String> lang = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");
lang.stream()
.sorted()
.collect(Collectors.toList());
// ["Go", "Groovy", "Java", "Python", "Scala", "Swift"]
lang.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// ["Swift", "Scala", "Python", "Java", "Groovy", "Go"]
가공 (peek)
Stream<T> peek(Consumer<? super T> action);
peek 메서드는 스트림 내 요소들을 대상으로 map 메서드처럼 연산을 수행한다. 하지만 새로운 스트림을 생성하지는 않고 인자를 받은 람다를 적용하기만 한다.
int sum = IntStream.of(1, 3, 5, 7, 9)
.peek(System.out::println)
.sum();
결과 만들기
가공한 스트림을 가지고 내가 사용할 결과값으로 만들어내는 단계다. 스트림을 끝내는 최종 작업이다.
결과 만들기 (calculating)
총합, 최대값, 최솟값, 숫자 개수, 평균값 등에 대한 계산을 수행한다.
→ sum, min, max, count, average
최대, 최소, 평균값의 경우 스트림이 비어있을 경우, 표현할 수 없기 때문에 Optional 을 이용해 리턴한다.
결과 만들기 (reduce)
reduce 메서드는 파리미터에 따라 3가지 종류가 있다.
- accumulator : 스트림의 요소들의 값을 누적시킨다.
- identity : accumulator 함수로 누적하지만 초기값이 존재한다.
- combiner : 병렬 스트림에서 나눠서 계산한 결과를 하나로 합치는 동작을 수행한다.
결과 만들기 (collect)
스트림은 collect 메서드를 이용해 나오는 데이터들을 컬렉션으로 모을 수 있다. collect 메소드에 Collector 메서드를 사용할 수 있다.
List<String> fruit = Arrays.asList("Banana", "Apple", "Grape");
String returnValue = fruit.stream()
.collect(Collectors.joining());
//BananaAppleGrape
Reference
'Backend Roadmap > Java' 카테고리의 다른 글
정적 바인딩과 동적 바인딩 (0) | 2024.06.25 |
---|---|
가비지 컬렉션(Garbage Collection) (0) | 2024.06.25 |
오토 박싱과 오토 언박싱 (0) | 2024.06.25 |
자바 타입 제네릭(Generic) (0) | 2024.06.25 |
JVM 구조, JRE, JDK (0) | 2024.06.25 |