1. 스트림 API
1.1 스트림 API 개념
- 스트림 사용 이전(과거)
- 많은 양의 데이터를 저장하기 위한 배열, 컬렉션 등의 데이터 접근 위해서는 반복문이나 반복자를 사용
→ 코드 길이 긺, 가독성 떨어짐, 코드 재사용X
- [ 해결법 ] : 스트림(stream) - 정의
- 데이터를 추상화하며 다룸
- 다양한 방식으로 저장된 데이터를 읽고 쓰기 위한 공통된 방법 제공
≫ 파일에 저장된 다양한 형태의 데이터들을 모두 같은 방법으로 다룰 수 있음 - 특징
스트림 | 컬렉션 |
내부 반복을 통해 작업 | 외부 반복을 통해 작업 |
단 한번만 사용 | 재사용 가능 |
원본 데이터를 변경하지 않음 | |
필터-맵(filter-map) 기반의 API 사용하여 지연(lazy) 연산을 통해 성능 최적화 |
|
parallelStream() 메소드를 통한 손쉬운 병렬처리 지원 |
- 동작 흐름
1. 스트림의 생성
2. 스트림의 중개 연산 (스트림의 변환)
3. 스트림의 최종 연산 (스트림의 사용)
1.2 스트림의 생성
- 컬렉션
- 모든 컬렉션의 최고 조상인 Collection 인터페이스에 stream() 메소드 정의
- parallelStream() 메소드를 사용 → 병렬 처리가 가능한 스트림 생성
- Stream 클래스의 forEach() 메소드는 해당 스트림의 요소를 하나씩 소모하며, 순차적으로 요소에 접근하는 메소드
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(4);
list.add(2);
list.add(3);
list.add(1);
// 컬렉션에서 스트림 생성
Stream<Integer> stream = list.stream();
// forEach() 메소드를 이용한 스트림 요소의 순차 접근
stream.forEach(System.out::println);
## 실행 결과 ##
4
2
3
1
- 배열
- Arrays 클래스에 stream() 메소드가 클래스 메소드로 정의
- 기본 타입인 int, long, double 형을 저장할 수 있는 배열에 관한 스트림 정의
→ java.util.stream 패키지의 IntStream, LongStream, DoubleStream 인터페이스로 각각 제공
String[] arr = new String[]{"넷", "둘", "셋", "하나"};
// 배열에서 스트림 생성
Stream<String> stream1 = Arrays.stream(arr);
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();
// 배열의 특정 부분만을 이용한 스트림 생성
Stream<String> stream2 = Arrays.stream(arr, 1, 3);
stream2.forEach(e -> System.out.print(e + " "));
## 실행 결과 ##
넷 둘 셋 하나
둘 셋
- 가변 매개변수(variable parameter)
- Stream클래스의 of() 메소드를 사용하면 가변 매개변수를 전달 받아 스트림 생성
// 가변 매개변수에서 스트림 생성
Stream<Double> stream = Stream.of(4.2, 2.5, 3.1, 1.9);
stream.forEach(System.out::println);
## 실행결과 ##
4.2
2.5
3.1
1.9
- 지정된 범위의 연속된 정수
- IntStream, LongStream 인터페이스의 range()와 rangeClosed() 메소드 사용
- range() 메소드 : 명시된 시작 정수를 포함O, 명시된 마지막 정수 포함X
- rangeClosed() 메소드 : 명시된 시작 정수, 명시된 마지막 정수 모두 포함O
// 지정된 범위의 연속된 정수에서 스트림 생성
IntStream stream1 = IntStream.range(1, 4);
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();
IntStream stream2 = IntStream.rangeClosed(1, 4);
stream2.forEach(e -> System.out.print(e + " "));
## 실행결과 ##
1 2 3
1 2 3 4
- 특정 타입의 난수들
- Random 클래스의 ints(), longs(), doubles()와 같은 메소드 사용
- 위의 메소드들은 매개변수로 스트림의 크기를 long 타입으로 전달 받음
- 매개변수 전달받지 않으면, 크기가 정해지지 않은 무한 스트림 반환
→ limit() 메소드를 사용하여 스트림 크기 제한 필요
// 특정 타입의 난수로 이루어진 스트림 생성
IntStream stream = new Random().ints(4);
stream.forEach(System.out::println);
## 실행결과 ##
1072176871
-649065206
133298431
-616174137
- 람다 표현식
- 람다 표현식을 매개변수로 전달받아 해당 람다 표현식에 의해 반환되는 값을 요소로 하는 무한 스트림 생성하기 위해 Stream 클래스의 iterate()와 generate() 메소드 정의
- interate() 메소드는 시드(seed)로 명시된 값을 람다 표현식에 사용하여 반환된 값을 다시 시드로 사용하는 방식으로 무한 스트림 생성
- generate() 메소드는 매개변수가 없는 람다 표현식을 사용하여 반환된 값으로 무한 스트림 생성
IntStream stream = Stream.iterate(2, n -> n + 2); // 2, 4, 6, 8, 10, ...
- 파일
- 파일의 한 행을 요소로 하는 스트림 생성위해 java.nio.file.Files 클래스의 lines() 메소드 사용
- java.io.BufferedReader 클래스의 lines() 메소드 사용하면 파일뿐만 아니라 다른 입력으로부터도 데이터를 행(line) 단위로 읽기
String<String> stream = Files.lines(Path path);
- 빈 스트림
- 아무 요소도 가지지 않는 빈 스트림은 Stream 클래스의 empty() 메소드를 사용하여 생성 가능
// 빈 스트림 생성
Stream<Object> stream = Stream.empty();
System.out.println(stream.count()); // 스트림의 요소의 총 개수를 출력함.
## 실행 결과 ##
0
1.3 스트림의 중개 연산
- 개념
- 생성된 초기 스트림은 중개 연산을 통해 또 다른 스트림으로 변환
- 스트림을 전달받아 스트림을 반환하므로, 연속으로 연결해서 사용 가능
- 필터-맵(filter-map) 기반의 API를 사용함으로 지연(lazy) 연산을 통해 성능을 최적화 가능 - 대표적인 중개연산과 메소드
1. 스트림 필터링 : filter(), distinct()
2. 스트림 변환 : map(), flatMap()
3. 스트림 제한 : limit(), skip()
4. 스트림 정렬 : sorted()
5. 스트림 연산 결과 확인 : peek()
- 스트림 필터링
- filter() 메소드 : 주어진 조건에 맞는 요소만으로 구성된 새로운 스트림 반환
- distinct() 메소드 : 중복된 요소가 제거된 새로운 스트림 반환
IntStream stream1 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
IntStream stream2 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
// 스트림에서 중복된 요소를 제거함.
stream1.distinct().forEach(e -> System.out.print(e + " "));
System.out.println();
// 스트림에서 홀수만을 골라냄.
stream2.filter(n -> n % 2 != 0).forEach(e -> System.out.print(e + " "));
## 실행결과 ##
7 5 2 1 3 4 6
7 5 5 1 3 5
- 스트림 변환
- map() 메소드 : 해당 스트림 요소들을 주어진 함수에 인수로 전달하여, 그 반환값들로 이루어진 새로운 스트림 반환
- flatMap() 메소드 : 스트림 요소가 배열일 때, 각 배열의 각 요소의 반환값을 하나로 합친 새로운 스트림 반환
Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "JAVASCRIPT");
stream.map(s -> s.length()).forEach(System.out::println);
## 실행결과 ##
4
3
4
10
String[] arr = {"I study hard", "You study JAVA", "I am hungry"};
Stream<String> stream = Arrays.stream(arr);
stream.flatMap(s -> Stream.of(s.split(" "))).forEach(System.out::println);
## 실행결과 ##
I
study
hard
You
study
JAVA
I
am
hungry
- 스트림 제한
- limit() 메소드 : 첫 번째 요소부터 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림 반환
- skip() 메소드 : 첫 번째 요소부터 전달된 개수만큼의 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소만으로 새로운 스트림 반환
IntStream stream1 = IntStream.range(0, 10);
IntStream stream2 = IntStream.range(0, 10);
IntStream stream3 = IntStream.range(0, 10);
stream1.skip(4).forEach(n -> System.out.print(n + " "));
System.out.println();
stream2.limit(5).forEach(n -> System.out.print(n + " "));
System.out.println();
stream3.skip(3).limit(5).forEach(n -> System.out.print(n + " "));
## 실행결과 ##
4 5 6 7 8 9
0 1 2 3 4
3 4 5 6 7
- 스트림 정렬
- sorted() 메소드 : 주어진 비교자(comparator)를 이용하여 정렬, 비교자 전달하지 않을 시 사전순으로 정렬
Stream<String> stream1 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
Stream<String> stream2 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
stream1.sorted().forEach(s -> System.out.print(s + " "));
System.out.println();
stream2.sorted(Comparator.reverseOrder()).forEach(s -> System.out.print(s + " "));
## 실행결과 ##
CSS HTML JAVA JAVASCRIPT
JAVASCRIPT JAVA HTML CSS
- 스트림 연산 결과 확인
- peek() 메소드 : 결과 스트림으로부터 요소를 소모하여 추가로 명시된 동작 수행
원본 스트림에서 요소를 소모X
연산과 연산 사이에 결과 확인 시 사용, 디버깅 용도
IntStream stream = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
stream.peek(s -> System.out.println("원본 스트림 : " + s))
.skip(2)
.peek(s -> System.out.println("skip(2) 실행 후 : " + s))
.limit(5)
.peek(s -> System.out.println("limit(5) 실행 후 : " + s))
.sorted()
.peek(s -> System.out.println("sorted() 실행 후 : " + s))
.forEach(n -> System.out.println(n));
## 실행결과 ##
원본 스트림 : 7
원본 스트림 : 5
원본 스트림 : 5
skip(2) 실행 후 : 5
limit(5) 실행 후 : 5
원본 스트림 : 2
skip(2) 실행 후 : 2
limit(5) 실행 후 : 2
원본 스트림 : 1
skip(2) 실행 후 : 1
limit(5) 실행 후 : 1
원본 스트림 : 2
skip(2) 실행 후 : 2
limit(5) 실행 후 : 2
원본 스트림 : 3
skip(2) 실행 후 : 3
limit(5) 실행 후 : 3
sorted() 실행 후 : 1
1
sorted() 실행 후 : 2
2
sorted() 실행 후 : 2
2
sorted() 실행 후 : 3
3
sorted() 실행 후 : 5
5
1.4 스트림의 최종 연산
- 개념
- 중개 연산을 통해 변환된 스트림이 최종 연산을 통해 각 요소를 소모하여 결과 표시
- 최종 연산 시에 모든 요소를 소모한 해당 스트림은 더는 사용할 수 없음 - 대표적인 최종 연산과 메소드
- 요소의 출력 : forEach()
- 요소의 소모 : reduce()
- 요소의 검색 : findFirst(), findAny()
- 요소의 검사 : anyMatch(), allMatch(), noneMatch()
- 요소의 통계 : count(), min(), max()
- 요소의 연산 : sum(), average()
- 요소의 수집 : collect()
- 요소의 출력
- forEach() 메소드 : 각 요소를 소모하여 명시된 동작 수행, 반환 타입 void
Stream<String> stream = Stream.of("넷", "둘", "셋", "하나");
stream.forEach(System.out::println);
## 실행결과 ##
넷
둘
셋
하나
- 요소의 소모
- reduce() 메소드
- 첫 번째와 두 번째 요소를 가지고 연산 수행 후, 그 결과와 세 번째 요소를 가지고 또 다시 연산 수행
- 모든 요소를 소모하여 연산 수행하고, 그 결과 반환
- 인수로 초깃값 전달 시, 해당 스트림의 첫 번째 요소와 연산 시작하며, 그 결과와 두 번째 요소를 가지고 계속해서 연산 수행
- reduce() 메소드
Stream<String> stream1 = Stream.of("넷", "둘", "셋", "하나");
Stream<String> stream2 = Stream.of("넷", "둘", "셋", "하나");
Optional<String> result1 = stream1.reduce((s1, s2) -> s1 + "++" + s2);
result1.ifPresent(System.out::println);
String result2 = stream2.reduce("시작", (s1, s2) -> s1 + "++" + s2);
System.out.println(result2);
## 실행결과 ##
넷++둘++셋++하나
시작++넷++둘++셋++하나
- 요소의 검색
- findFirst() 메소드, findAny() 메소드 : 첫 번째 요소를 참조하는 Optional 객체 반환
IntStream stream1 = IntStream.of(4, 2, 7, 3, 5, 1, 6);
IntStream stream2 = IntStream.of(4, 2, 7, 3, 5, 1, 6);
OptionalInt result1 = stream1.sorted().findFirst();
System.out.println(result1.getAsInt());
OptionalInt result2 = stream2.sorted().findAny();
System.out.println(result2.getAsInt());
## 실행결과 ##
1
1
- 요소의 검사
- anyMatch() 메소드 : 해당 스트림의 일부 요소가 특정 조건을 만족할 경우 true 반환
- allMatch() 메소드 : 해당 스트림의 모든 요소가 특정 조건을 만족할 경우에 true 반환
- noneMatch() 메소드 : 해당 스트림의 모든 요소가 특정 조건을 만족하지 않을 경우 true 반환
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.anyMatch(n -> n > 80));
System.out.println(stream2.allMatch(n -> n > 80));
## 실행결과 ##
true
true
- 요소의 통계
- count() 메소드 : 요소의 총 개수를 long 타입 값으로 반환
- max(), min() 메소드 : 요소 중에서 가장 큰 값과 가장 작은 값을 가지는 요소를 참조하는 Optional 객체 반환
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.count());
System.out.println(stream2.max().getAsInt());
## 실행결과 ##
4
90
- 요소의 연산
- sum() 메소드 : 모든 요소의 합 반환
- average() 메소드 : 모든 요소의 평균을 각 기본타입으로 래핑 된 Optional 객체로 반환
IntStream stream1 = IntStream.of(30, 90, 70, 10);
DoubleStream stream2 = DoubleStream.of(30.3, 90.9, 70.7, 10.1);
System.out.println(stream1.sum());
System.out.println(stream2.average().getAsDouble());
## 실행결과 #3
200
50.5
- 요소의 수집
- collect() 메소드 : 인수로 전달되는 Collectors 객체에 구현된 방법대로 스트림 요소 수집
- Collectors 메소드
1. 스트림을 배열이나 컬렉션으로 변환 : toArray(), toCollection(), toList(), toSet(), toMap()
2. 요소의 통계와 연산 메소드와 같은 동작을 수행 : counting(), maxBy(), minBy(), summingInt(), averagingInt() 등
3. 요소의 소모와 같은 동작을 수행 : reducing(), joining()
4. 요소의 그룹화와 분할 : groupingBy(), partitioningBy()
Stream<String> stream = Stream.of("넷", "둘", "하나", "셋");
List<String> list = stream.collect(Collectors.toList());
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
System.out.print(iter.next() + " ");
}
## 실행결과 ##
넷 둘 하나 셋
Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "PHP");
Map<Boolean, List<String>> patition = stream.collect(Collectors.partitioningBy(s -> (s.length() % 2) == 0));
List<String> oddLengthList = patition.get(false);
System.out.println(oddLengthList);
List<String> evenLengthList = patition.get(true);
System.out.println(evenLengthList);
## 실행결과 ##
[CSS, PHP]
[HTML, JAVA]
[ 출처 ]
https://tcpschool.com/java/java_stream_concept
'JAVA' 카테고리의 다른 글
Optional 클래스 (0) | 2021.08.04 |
---|---|
CH07. 제네릭, 열거형, 컬렉션 (0) | 2021.08.04 |
CH06. java.lang 패키지와 유용한 클래스 (0) | 2021.05.10 |
[Tip] static 메서드의 오버라이딩 불가 (0) | 2021.05.07 |
CH05. 상속, 오버라이딩, package, 제어자, 다형성, 추상클래스, 인터페이스 (0) | 2021.05.03 |
댓글