스트림(Stream) API
스트림 API의 등장 배경
자바에서는 많은 양의 데이터를 저장하기 위해 배열이나 컬렉션을 사용
→ 접근하기 위해서는 반복문이나 반복자(iterator)를 사용하여 매번 새로운 코드를 작성
→ 작성된 코드의 길이가 너무 길고 가독성이 떨어지며, 재사용이 거의 불가능
→ 이러한 문제점을 극복하기 위해서 Java SE 8부터 스트림(stream) API 도입
· 자바 8부터 지원되기 시작한 람다를 활용할 수 있는 기술 중 하나
· 프로그램과 입출력 장치 사이의 매개자 역할을 하는 스트림과는 전혀 다른 개념
· 다양한 데이터들을 읽고 쓰기 위한 공통된 방법 제공
· 내부 반복(internal iteration)을 통해 작업 수행 ↔ 컬렉션 : 외부 반복
· 원본 데이터를 변경X → 복사본
· parallelStream() 메소드를 통해 손쉬운 병렬 처리 가능
스트림 API의 동작 흐름
큰 흐름은 다음과 같다.
생성 → 가공(중간 연산) → 결과 만들기(결과 연산)
생성
대표적으로 배열과 컬렉션을 이용하여 스트림 인스턴스를 생성
Stream<Integer> arrayStream1 = Stream.of(1, 2, 3);
Stream<Integer> arrayStream2 = Arrays.stream(new Integer[]{1, 2, 3});
Stream<String> arrayStream3 = Stream.of("a", "b", "c");
Stream<String> arrayStream4 = Arrays.stream(new String[]{"a", "b", "c"});
Stream.builder()를 사용하여 직접적으로 원하는 값을 넣어 스트림을 생성하는 것도 가능하고 Stream.empty()를 사용하여 비어있는 스트림도 생성 가능하다. 이 외에도 다양한 방법으로 스트림을 만들 수 있다.
가공
필터링, 맵핑 등을 이용하여 원하는 결과를 만들어가는 중간 작업
1. Filtering
스트림 내 요소들을 하나씩 평가해서 조건에 맞춰 걸러내는 작업
String[] str = {"James", "Aiden", "Eva", "Bob"};
Stream<String> stream = Arrays.stream(str)
.filter(name -> name.contains("a"));
// 'a'를 포함한 문자열만 리턴
// 출력 결과 : [James, Eva]
2. Mapping
스트림 내 요소들을 하나씩 특정 값으로 변환하는 작업. 값을 변환해주는 람다식을 인자로 받는다.
String[] str = {"James", "Aiden", "Eva", "Bob"};
Stream<String> stream = Arrays.stream(str)
.map(String::toUpperCase);
// 대문자로 변환
// 출력 결과 : [JAMES, AIDEN, EVA, BOB]
List<Integer> numbers = new ArrayList<>();
numbers.add(10); numbers.add(20); numbers.add(30);
String str2 = numbers.stream()
.map(e -> "" + e)
.collect(Collectors.joining(", "));
// 문자열로 변환
// 출력 결과 : 10, 20, 30
Double[] num = Arrays.stream(new String[]{"10", "20", "30"})
.mapToDouble(Double::parseDouble)
.boxed()
.toArray(Double[]::new);
// 실수로 변환
//출력 결과 : [10.0, 20.0, 30.0]
3. Sorting
스트림 내 요소들을 비교하여 정렬해주는 작업
List<Integer> list = IntStream.of(1, 15, 30, 2, 60, 5, 90)
.sorted()
.boxed()
.toList();
// 오름차순 정렬
// 출력 결과 : [1, 2, 5, 15, 30, 60, 90]
List<String> list2 = Arrays.asList("Aiden", "James", "Duke", "Amy", "Bob");
list2 = list2.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// 문자열 내림차순 정렬
// 출력 결과 : [James, Duke, Bob, Amy, Aiden]
4. Peek
스트림 내 요소들을 대상으로 연산을 수행하지 않고 인자로 받은 람다식을 수행한다.
데이터들 변형 X
int sum = IntStream.range(1, 100)
.peek(System.out::println)
.sum();
// 출력 결과 : 1\\n2\\n ... 99\\n
결과 만들기
계산 결과나 리스트 변환, 문자열 붙이기 등의 최종적으로 사용할 결과값을 만들어내는 최종 작업
1. Calculating
최소, 최대, 합, 평균 등의 기본형 타입으로 결과를 만들어 내는 작업
long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();
OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();
DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
.average()
.ifPresent(System.out::println);
// 평균, 최소, 최대의 경우 NullPointerException 예외처리를 위해 Optional 사용
2. Collecting
Collector 타입의 인자를 받아서 처리하는 종료 작업 중 하나
List<String> list = Arrays.asList("Apple", "Orange", "Grape", "Banana");
list2 = list2.stream()
.sorted()
.collect(Collectors.toList());
// 리스트로 변환
String output = noDatum
.stream()
.map(e -> e.getId() + "")
.collect(Collectors.joining(" "));
// 공백을 넣어서 하나의 스트링으로 이어붙이기