Java Stream API
💬 Java Stream API란 무엇인가요?
✅ (정리 중)
Stream API란?
📌 데이터를 추상화하고, 처리하는데 자주 사용되는 함수를 정의해 둔 것.
JAVA
JAVA는 객체지향 언어이기 때문에, 기본적으로 함수형 프로그래밍이 불가능하다. 하지만 JAVA8부터 Stream API와 람다식, 함수형 인터페이스 등을 지원하면서 JAVA를 지원함으로써 함수형으로 프로그래밍을 할 수 있는 API들을 지원해주고 있다. 그중에서 Stream API는 위에 설명했듯이 데이터를 추상화하고 처리하는 데 사용된다.
즉, 데이터의 종류에 상관없이 같은 방식으로 데이터를 처리할 수 있다는 것을 의미하며, 그에 따라 코드의 재사용성을 높일 수 있다.
예시 코드
- Stream 활용 전 : 원본의 데이터가 직접 정렬됨
String[] nameArr = {"yana", "winston", "daijee"};
List<String> nameList = Arrays.asList(nameArr);
//원본의 데이터가 직접 정렬됨
Arrays.sort(nameArr);
Collection.sort(nameList);
for (String str : nameArr) {
System.out.println(str);
}
for (String str : nameList) {
System.out.println(str);
}
- Stream 활용 후 : 원본의 데이터가 아닌 별도의 Stream을 생성하고, 복사된 데이터를 정렬하여 출력함.
- 가독성 있게 정리 가능
- 원본의 데이터에 변형을 가하지 않음
- 일회용임
- 내부 반복으로 작업을 처리함
String[] nameArr = {"yana", "winston", "daysee"}
List<String> nameList = Arrays.asList(nameArr);
Stream<String> nameStream = nameList.stream();
Stream<String> arrayStream = Arrays.stream(nameArr);
nameStream.sorted().forEach(System.out::println);
arrayStream.sorted().forEach(System.out::println);
1. 원본의 데이터에 변형을 가하지 않음
Stream API는 원본의 데이터를 가공하는 것이 아닌 단순히 조회만 하며, 별도의 Stream을 생성하여 정렬이나 필터링 등의 작업을 처리한다.
2. 일회용
위에서 명시하였듯이, Stream API는 데이터를 처리할 때 Stream을 생성해 사용하는데 이는 일회용이며, 한번 사용하면 재사용이 불가능하다. 따라서 필요시에는 Stream을 다시 생성해주어야 한다.
만약 닫힌 Stream을 다시 사용한다면 IllegalStateException이 발생하게 된다.
userStream.sorted().forEach(System.out::print);
// 스트림이 이미 사용되어 닫혔으므로 에러 발생
int count = userStream.count();
// IllegalStateException 발생
java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
3. 내부 반복으로 작업을 처리함
기존에는 반복문을 사용하기 위해 for문 혹은 while문 등과 같은 문법을 사용해야 했지만, stream에서는 반복 문법을 메서드 내부에 마련해 두었기에 간결한 코드 작성이 가능하다.
nameStream.forEach(System.out::println);
Stream API의 연산 종류
💡 Stream연산의 경우 데이터 처리를 위해 몇 가지 단계를 거쳐야 한다.
Stream의 경우 위에서 언급하였듯이, 원본의 데이터를 가공하지 않고 Stream을 생성해 가공하기 때문에
- Stream생성의 단계를 거치며
원하는 데이터가 나올 때까지 데이터를 가공하는 - 중간 연산 : 연산 결과가 스트림인 연산.(중복 가능)
마지막으로 Stream으로 되어있는 데이터를 원하는 원하는 형태로 만들거나, 원하는 연산을 진행하는 - 최종 연산 : 연산 결과가 Stream이 아닌 연산.(중복 불가능 → 스트림 재활용이 불가하기 때문)
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream() // 생성하기
.filter(s -> s.startsWith("c")) // 가공하기
.map(String::toUpperCase) // 가공하기
.sorted() // 가공하기
.count(); // 결과만들기
Stream 연산의 종류
중간 연산 | 설명 |
Stream<T> distint() | 중복 제거 |
Stream<T> filter ( Predicate<T> predicate ) | 조건에 안 맞는 요소 제외 |
Stream<T> limit ( long maxSize) | 스트림의 일부를 잘라냄 |
Stream<T> skip ( long n ) | 스트림의 일부를 건너뜀 |
Stream<T> peek ( Consumer<T> action ) | 스트림 요소에 작업수행 |
Stream<T> sorted ( ) Stream<T> sorted ( Comparator<T> comparator ) | 스트림의 요소 정렬 |
Stream<R> map (Function<T,R> mapper ) DoubleStream mapToDouble ( ToDoubleFunction<T> mapper ) IntStream mapToInt ( ToIntFunction<T> mapper ) LongStream mapToLong ( ToLongFunction<T> mapper ) Stream<R> flatMap (Function<T,Stream<R>> mapper ) DoubleStream flatMapToDouble ( Function<T, DoubleStream> m ) IntStream flatMapToInt ( Function<T, IntStream> m ) LongStream flatMapToLong ( Function<T, LongStream> m ) |
스트림의 요소를 변환 |
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 ( ) //첫 번째 요소 | 스트림 요소 1개 반환 |
boolean allMatch ( Predicate<T> p ) //모두 boolean anyMatch ( Predicate<T> p ) //하나라도 boolean noneMatch ( Predicate<T> p ) //모두 만족하지 않음 |
주어진 조건 만족여부 반환 |
Object[] toArray ( ) A[] toArray ( IntFunction<A[]> generator ) | 스트림 모든 요소 배열로 반환 |
Optinal<T> reduce (BinaryOperator<T> accumulator ) T reduce (T identity, BinaryOperator<T> accumulator ) U reduce (U identity, BinaryOperator<U,T,U> accumulator, BinaryOperator<U> combiner ) |
스트림 요소를 하나씩 줄여가며(리듀싱) 계산 |
R collect (Collector<T,A,R> collector ) R collect (Supplier<R> supplier, BiConsumer<R,T> accumulator, BiConsumer<R,R> cimbiner ) |
스트림 요소 수집. 요소를 그룹화 하거나, 분한한 결과를 컬렉션에 담아 반환하는데 주로 사용. |
지연된 연산
스트림 연산에서, 최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않음. 스트림에 대해 distint() 나 sort()와 같은 중간 연산을 호출해도, 즉각적으로 연산이 수행되는 것이 아님.
Stream <Integer>와 IntStream
스트림의 요소 타입은 T. 즉 Stream <T>. 오토박싱 & 언박싱으로 인한 비효율을 줄이기 위해 데이터 소스의 요소를 기본형으로 IntStream, LongStream, DoubleStream이 제공됨. 따라서 일반적으로 Stream <Integer>보다 Intstream을 사용하는 것이 더 효율적. ( Intstream에 int 타입의 값을 작업하는데 유용한 메서드가 포함되었기 때문)
병렬처리 스트림, 병렬 스트림
Stream에 parallel()이라는 메서드를 호출해 병렬로 연산을 수행할 수 있음. 반대로 sequential()을 호출하면 병렬로 처리되지 않게 설정할 수 있음(이는 병렬처리 함수 parallel()를 무효화하는 데에만 쓰임.)
스트림 생성
컬렉션
컬렉션의 최고 조상인 Collection에 stream()이 정의되어 있음. 따라서 Collection의 자손인 List와 Set을 구현한 클래스들은 모두 해당 메서드로 스트림 생성 가능.
List<Integer> list = Arrays.asList (1,2,3,4,5); //가변인자
Stream<Integer> intStream = list.stream(); //list를 소스로 하는 Stream 생성
intStream.forEach(System.out::println);
배열
배열을 소스로 하는 스트림을 생성하는 메서드는, Stream과 Arrays에 있는 static메서드로 정의되어 있음.
Stream<T> Stream.of(T... values)
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive)
따라서 String Stream은 다음과 같이 생성할 수 있다.
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)
참고자료
'Web_Backend > Java' 카테고리의 다른 글
[JAVA] 재귀함수 vs 반복문 (0) | 2023.06.20 |
---|---|
[JAVA] GC(Garbage collector, Garbage collection) (0) | 2023.06.15 |
[JAVA] String, String Builder, String buffer 차이 (0) | 2023.03.19 |
[초보 개발자의 도서 리뷰] "곁에 끼고 자주 찾아볼 자바 교보재" 코딩 개념 잡는 자바 코딩 문제집 (0) | 2022.12.02 |
[Java의 정석] 08 예외처리(Exception Handling) (0) | 2022.01.31 |
야나의 코딩 일기장 :) #코딩블로그 #기술블로그 #코딩 #조금씩,꾸준히
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!