티스토리 뷰
스트림을 배우기 전에 function 패키지도 같이 배워 보기 위해서 작성하게 되었다.
어떻게 사용하는지를 배우고 나서 실제 예시 코드 순서로 공부하도록 하자!
function 패키지: 함수형 프로그래밍의 활용
JAVA API인 java.util.function 패키지는 이후에 함수형 프로그래밍을 하기 위해서 도입된 함수형 인터페이스입니다.
앞으로 배울 것들을 앞서 미리 배워야 하는 함수형 인터페이스입니다.
이렇게 알아두시면 좋습니다.
이건 인터페이스이므로 이 자리에는 내용물있는 함수(=구현체)가 와서 뭘 실행하겠구나라고 생각해 주세요!
함수형 | 인터페이스 메서드 | 설 명 |
Runnable | [ void run() ] | 매개변수도 없고, 반환 값도 없다. |
Supplier<T> | [ T get() ] → T | 매개변수는 없고, 반환 값만 있다. |
Consumer<T> | T → [ void accept(T t) ] | 매개변수만 있고, 반환 값은 없다. |
Function<T, R> | T → [ R apply(T t) ] → R | 일반적인 함수이다. 하나의 매개변수를 받아서 결과를 반환한다. |
Predicate<T> | T → [ boolean test(T t) ] → boolean | 조건식을 표현하는데 사용된다. 매개변수는 하나이고 반환 타입은 Boolean 값이다. |
스트림 (Stream)
스트림이란 자바 8 (= JAVA 1.8)부터 도입된 기능으로 컬렉션(List, Set, Map)과 배열 등의
다양한 데이터를 다루는 연산을 간단하고 효율적으로 처리할 수 있게 하는 기능입니다.
예를 들어 List를 정렬 할 때 Collections.sort()를 사용했던 것을 스트림은 데이터를 추상화하고, 정렬하게 합니다.
즉, 스트림은 데이터를 다루는데 자주 사용되는 메서드를 정의해 놓아서 문제점을 해결한 기능입니다.
스트림 적용 전
List<String> strList = Arrays.asList(new String[]{"aa", "bb", "cc"});
for(String str : strList) {
if(str.length() < 5) {
System.out.println(str);
}
}
// 출력
// aa
// bb
// cc
스트림 적용 후
List<String> strList = Arrays.asList(new String[]{"aa", "bb", "cc"});
strList.stream().filter(str -> str.length() < 5).forEach(System.out::println);
// 출력
// aa
// bb
// cc
뭔가가 3,4줄로 작성하던 것이 한 줄로 끝냈다고?
연산된 데이터를 if으로 쓰고 또 가공하고를 스트림으로 한 번에 처리할 수 있다니 되게 좋아졌네요~!
하.지.만 스트림을 쓴다해서 성능이 좋아지는 것은 아닌 것을 꼭 기억해 주시고 가독성을 향상시키기 위해 사용한다는 것을 인지해 주세요!
스트림의 특징
1. 원본 데이터 소스를 변경을 안 한다고?
스트림은 데이터 소스로 부터 데이터를 읽기만 할 뿐. 원래 데이터 소스를 변경하지 않는다는 차이가 있습니다.
즉, 변경된 값(= 정렬된 값) sortedList와 원본데이터 strStream은 다릅니다.
왜냐하면! strStream에 있는 데이터는 변경되지 않았기 때문입니다.
// String을 담은 리스트를 스트림을 만든 strStream이 있다고 하자
List<String> sortedList = strStream.sorted().collect(Collectors.toList());
sortedList.stream() != strStream
2. 1회용이라구?
스트림은 Iterator처럼 일회용으로 사용합니다. 스트림을 한번 사용하면 닫혀서 다시 사용할 수 없게 됩니다.
// String을 담은 리스트를 스트림을 만든 strStream이 있다고 하자
strStream.sorted().forEach(System.out::println); // 반복문을 이용해서 스트림을 이미 출력!
int numOfStr = strStream.count(); // Error! 스트림이 닫혔다!
3. 최종 연산 하기 전까지는 중간연산이 수행되질 않아!
중간연산을 수행하면서 데이터를 변환하고 최종 연산을 바로 주는 것이 아닌 지연된 연산으로 최종 연산 때 중간 연산이 수행한다.
4. 기본형 스트림을 사용하자
오토박싱&언박싱의 비효율을 제거하기 위해 Stream<Integer>보다는 IntStream을 사용한다.
LongStream, DoubleStream도 포함이 된다.
스트림의 연산 구조
Tip) Collection에 Stream이 정의되어 있어서 스트림은 컬렉션을 소스로 한 것으로 생성합니다.!
중간 연산 : 연산 결과가 스트림인 연산이다. 스트림에 연속해서 중간 연산할 수 있다.
최종 연산 : 연산 결과가 스트림이 아닌 연산이다. 스트림의 요소를 소모하므로 단 한 번만 가능하다.
중간 연산 목록
중간 연산 | 설명 |
Stream<T> distinct() | 중복을 제거한다. |
Stream<T> filter(Predicate<T> predicate) | 조건에 안맞는 요소는 제외한다. |
Stream<T> limit(long maxSize) | 스트림의 일부를 잘라낸다. |
Stream<T> skip(long n) | 스트림의 일부를 건너뛴다. |
Stream<T> peek(Consumer<T> action) | 스트림의 요소에 작업을 수행한다. (스트림 내용 변경x) 보통 디버깅이나 로깅정도로 사용됩니다. |
Stream<T> sorted(Comparator<T> comparator) | 스트림의 요소를 정렬 |
Stream<R> map(Function<T,R> mapper) | 스트림의 요소를 변환한다. (스트림 내용 변경 o) |
최종 연산 목록
최종 연산 | 설명 |
void forEach(Consumer<? super T> action void forEachOrdered(Consumer<? super T> action) |
각 요소에 지정된 작업 수행한다. |
long count() | 스트림의 요소의 개수 반환한다. |
Optional<T> max(Comparator<? super T> comparator) Optional<T> min(Comparator<? super T> comparator) |
스트림의 최대값/최소값을 반환한다. |
Optional<T> findAny() Optional<T> findFirst() |
아무거나 하나 반환한다. 첫 번째 요소를 반환한다. |
boolean allMatch(Predicate<T> p) boolean anyMatch(Predicate<T> p) boolean noneMatch(Predicate<> p) |
모두 만족하는지 확인한다. 하나라도 만족하는지 확인한다. 모두 만족하지 않는지 확인한다. |
Object[] toArray() List toList() Set toSet |
스트림의 모든 요소를 배열로 반환한다. 스트림의 모든 요소를 List로 반환한다. 스트림의 모든 요소를 Set로 반환한다. |
Optional<T> reduce(BinaryOperator<T> accumulator) | 스트림의 요소를 하나씩 줄여가면서 (리듀싱) 계산한다. |
R collect(Collector<T,A,R> collector) ex) collect(Collectors.toSet()) |
스트림의 요소를 수집한다. |
스트림은 앞으로 코딩테스트와 Collection데이터를 정리할 때 자주 사용하게 될 것입니다.
어떻게 사용하는지 그리고 코드를 봤을 때 이해만 할 수 있는 정도면 됩니다!!
이제 조금 어려운 부분을 상세하게 배워 볼까요?
그러곤 사용 예시 코드들을 보여주겠습니다!
최종 연산 reduce()
스트림의 요소를 하나씩 줄여가며 누적연산을 수행하는 메서드입니다.
reduce메서드 구조
T reduce(T identity, BinaryOperator<T> accumulator)
- identity : 초기값
- accumulator : 이전 연산 결과와 스트림의 요소에 수행할 연산
reduce메서드 사용 예시
int count = intStream.reduce(0, (a, b) -> a + 1); // count()
int sum = intStream.reduce(0, (a, b) -> a + b); // sum()
/* sum() 내부 상황
int a = identity;
for(int b : stream)
a = a + b;
*/
int max = intStream.reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b); // max()
int min = intStream.reduce(Integer.MAX_VALUE, (a, b) -> a > b ? a : b); // min()
최종 연산 collect()
결국에는 스트림의 요소들을 수집하여 원하는 형태로 변환하거나 그룹화하는 데 사용되는 최종 연산입니다.
즉, 스트림의 요소들을 수집하여 컬렉션(List, Set, Map)이나 배열, 그룹화하여 집계된 결과를 얻을 때 유용하게 사용합니다.
collect 메서드 구조
Object collect(Collector collector)
Collector 인터페이스에 들어올 것들
- 컬렉션으로 변환 : Collectors.toList(), Collectors.toSet(), Collectors.toMap()
- 통계 : counting(), summingInt(), averagingInt()
- 문자열 결합 : joining()
- 그룹화 분할 : groupingBy(), partitioningBy()
스트림을 컬렉션으로 변환
Student객체로 이루어진 스트림이 있다고 가정하자!
import java.util.stream.Collectors;
import java.util.stream.Stream;
// ----------------경우 1
// 스트림 -> 리스트로 변환
List<String> names = studentStream.map(Student::getName).collect(Collectors.toList());
// 리스트에서 틀정 컬렉션으로 변환
ArrayList<String> list = names.stream().collect(Collectors.toCollection(ArrayList::new));
// ---------------- 경우 2
// 스트림에서 맵으로 변환
Map<String, Student> map = studentStream.collect(Collectors.toMap(p -> p.getId(), p -> p));
통계
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// 요소 개수 ( counting() )
long count = numbers.stream().collect(Collectors.counting());
// 합계 ( summingInt() )
int sum = numbers.stream().collect(Collectors.summingInt(Integer::intValue));
// 평균 ( averagingInt() )
double average = numbers.stream().collect(Collectors.averagingInt(Integer::intValue));
// 통계 요약 정보 ( summarizingInt() )
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
문자열 결합
String 으로 이루어진 List 컬렉션이 있다고 가정하자
// 문자열 결합
String joined = stringList.stream().collect(Collectors.joining(", "));
그룹화 분할
// 점수에 따른 학생 그룹화
Map<String, List<Student>> scoreGroup = students.stream()
.collect(Collectors.groupingBy(student -> student.getScore() >= 90 ? "A" : "B"));
// 점수에 따른 학생 분할
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(student -> student.getScore() >= 70));
스트림 사용해 보기
reduce를 사용하여 문자열을 하나의 문자열로 합치기
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
List<String> words = Arrays.asList("apple", "banana", "orange", "grape", "pear");
Optional<String> combined = words.stream().reduce((result, word) -> result + ", " + word);
combined.ifPresent(System.out::println);
// 출력 (아래로 한개씩 출력)
// apple, banana, orange, grape, pear
collect를 사용하여 글자 길이가 5보다 큰 단어를 리스트로 모으기
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
List<String> words = Arrays.asList("apple", "banana", "orange", "grape", "pear");
List<String> longWords = words.stream()
.filter(word -> word.length() > 5) // 중간 연산
.collect(Collectors.toList()); // 최종 연산
System.out.println("5글자보다 큰단어 : " + longWords);
// 출력
// 5글자보다 큰단어 : [banana, orange]
findAny를 사용하여 글자 길이가 5보다 큰 단어 찾기
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
Optional<String> anyLongWord = words.stream()
.filter(word -> word.length() > 5) // 중간 연산
.findAny(); // 최종 연산
anyLongWord.ifPresent(System.out::println);
// 출력
// banana
allMatch를 사용하여 모든 단어가 글자 길이 3보다 큰지 확인
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
List<String> words = Arrays.asList("apple", "banana", "orange", "grape", "pear");
List<String> longWords = words.stream()
.filter(word -> word.length() > 5) // 중간 연산
.collect(Collectors.toList()); // 최종 연산
System.out.println("5글자보다 큰단어 : " + longWords);
// 출력
// 5글자보다 큰단어 : [banana, orange]
map과 collect를 사용하여 단어 길이 리스트 만들기
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
List<String> words = Arrays.asList("apple", "banana", "orange", "grape", "pear");
List<Integer> wordLengths = words.stream()
.map(String::length) // 중간 연산
.collect(Collectors.toList()); // 최종 연산
System.out.println("Word lengths: " + wordLengths);
// 출력
// Word lengths: [5, 6, 6, 5, 4]
스트림의 변환
변환에는 어떤 메서드를 써야 하는지 정리해놓은 표입니다.
IntStream만 있는 곳은 Long, Double도 사용할 수 있습니다.
From | 변환 메서드 | To |
1. 스트림 → 기본형 스트림 | ||
Stream<T> | mapToInt (ToIntFunction mapper) mapToLong (ToLongFunction mapper) mapToDouble (ToDoubleFunction<T> mapper) |
IntStream LongStream DoubleStream |
2. 기본형 스트림 → 스트림 | ||
IntStream LongStream DoubleStream |
boxed () | Stream<Integer> Stream<Long> Stream<Double> |
3. 기본형 스트림 → 기본형 스트림 | ||
IntStream LongStream DoubleStream |
asLongStream () asDoubleStream () |
LongStream DoubleStream |
4. 스트림 → 부분 스트림 | ||
Stream<T> | skip (long n) limit (long maxSize) |
Stream<T> |
5. 두 개의 스트림 → 스트림 | ||
Stream<T>, Stream<T> | concat(Stream<T> a, Stream<T> b) | Stream<T> |
IntStream, IntStream | concat(IntStream a, IntStream b) | IntStream |
6. 스트림 → 병렬 스트림 & 병렬 스트림 → 스트림 | ||
Stream<T> IntStream |
parallel() // 스트림 → 병렬 스트림 sequential() // 병렬 스트림 → 스트림 |
Stream<T> IntStream |
7. 스트림 → 컬렉션 | ||
Stream<T> | collect (Collectors.toCollection (Supplier factory)) | Collection<T> |
collect (Collectors.toList()) | List<T> | |
collect (Collectors.toSet()) | Set<T> | |
8. 컬렉션 → 스트림 | ||
Collection<T> | stream() | Stream<T> |
9. 컬렉션 → 스트림 | ||
Stream<T> | collect (Collectors.toMap(Function Key, Function value)) | Map<K, V> |
감사합니다.
'프로그래밍 언어 > 💫JAVA' 카테고리의 다른 글
[JAVA] URL : 네트워크 통신과 리소스 접근을 위한 핵심 클래스 (0) | 2023.12.28 |
---|---|
[JAVA] Optional 옵션널 : 예측 불가능한 널 포인터 예외에서 벗어나다 (0) | 2023.12.04 |
[JAVA] Comparator 비교 : 데이터 정렬을 우리가 바꾸는 방법은? (0) | 2023.11.15 |
[JAVA] Enum 열거 : 코드의 가독성을 높이는 효과적인 선택 (0) | 2023.11.09 |
[JAVA] Generic 제네릭 : 안전성을 추가하고 사용하자 (0) | 2023.11.01 |
- Total
- Today
- Yesterday
- Fetch
- git
- 프로세스
- aws
- java
- 디자인패턴
- Spring Security
- 개발블로그
- 개발환경
- DBeaver
- spring
- 자바스크립트
- Cors
- Front
- 네트워크
- 프론트
- 템플릿
- Mac
- 개발자
- 코딩테스트
- 개발
- 오라클
- 비동기
- JavaScript
- AJAX
- 데이터 베이스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |