🏃♂️ 들어가기 앞서..
본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕
*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.
Stream
): 다양한 데이터 소스( Collections, Arrays )를 표준화된 방법으로 다루기 위한 것
물론
Collection
이나Iterator
와 같은 인터페이스를 통해 컬렉션을 다루는 방식이 표준화되어 있긴 하지만 "같은 기능의 메서드들 중복 정의 " 등과 같이 많이 부족한 면이 있다.
스트림(Stream)은
데이터 소스를 추상화하고
다루는데 자주 사용되는 메서드들을 정의해 놓았다.
추상화를 통해
데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있기 때문에
완전한 통일, 완전한 표준화를 이뤄낼 수 있었다.
자연스레 표준화가 되었기 때문에
코드의 재사용성이 높아지는 효과가 있다.
스트림을 이용하는 방법은
다음과 같다.
List<Integer> list = Arrays.asList(1,2,3,4,5) ; // 컬렉션
Stream<Integer> intStream = list.stream() ; // 컬렉션 -> Stream
Stream<String> strStream = Stream.of( new String[]{"a", "b", "c"} ) ; // 배열 -> Stream
Stream<Integer> evenStream = Stream.iterate(0, n->n+2) ; // 람다식 -> Stream
Stream<Double> randomStream = Stream.generate(Math::random) ; // 메서드 참조 : 람다식 -> Stream
// Int형 최적화 Stream : IntStream
IntStream intStream = new Random().ints(5) ; // 난수 스트림
※ Stream 작업 처리 3단계
: ① Stream 만들기 ▶ ② 중간 연산 (내부 반복 작업) : 0~n번 ▶ ③ 최종 연산 (결과 출력) : 0~1번
- 중간 연산 : [연산 결과] 스트림 _ 반복적으로 적용 가능
- 최종 연산 : [연산 결과] 스트림 X _ 단 한 번만 적용가능 (스트림 : 일회성 → 소모됨)
/* - distinct = 중복제거 - limit = ~개 까지만 - sorted = 정렬 - forEach = 각 요소마다 */ stream.distinct().limit(5).sorted().forEach(System.out::println) ; // |----------중간연산----------| |---------최종연산----------|
Read Only (Like SQL SELECT)
: DB에서 직접 데이터를 변경하지 안혹 조회만 하는 SELECT의 경우와 비슷하고
적용되는 메서드는 변경 메서드가 아닌 주로 조회/취급 조건을 거는데에 사용되는 메서드이다.
List<Integer> list = Arrays.asList(3,1,5,4,2) ;
List<Integer> sortedList = list.stream().sorted().collect(Collections.toList()); // 정렬해서 새로운 List에 저장
System.out.println(list); // [3,1,5,4,2]
// list에 .을 통해 여러 함수를 적용했음에도 실제로 변경되지 않는다.
System.out.println(sortedList) ; // [1,2,3,4,5]
Iterator 처럼 일회용으로
필요하면 다시 Stream을 생성해야 한다.
" 내부 반복 " : 단순하게 for문이나 while문 처럼 블록을 활용해서 반복 시키는 것이 아닌
forEach
와 같이 반복문을 내부에 숨긴 메서드들을 통해 간략하게 반복문을 처리하는 것
성능은 비효율적이지만
반복문을 메서드 안에 넣어버림으로서
스트림을 이용한 작업을 작성하는 코드가 간결해진다.
앞서 스트림은 생성 이후,
중간 연산과 최종 연산 단계를 거친다는 것을 알 수 있었다.
여기에서 지연된 연산이라는 것은
중간 연산 과정에서
각 연산을 하나씩 독립적으로 수행되는 것이 아니라
최종 연산 전까진 수행되지 않다가
최종 연산이 수행되어야
비로소 해당 스트림의 요소들이 중간 연산을 거쳐
최종 연산에서 소모된다.
Stream<Integer>
& IntStream
기본적으로
" 요소의 타입이 T인 스트림 " Stream<T>
으로 사용되는데
이 T에는 기본형이 아닌 참조형만 가능하다.
그래서 자세히 살펴보면
int[] intArr = { 1, 2, 3 } ;
Stream<Integer> intStream = Arrays.stream(intArr) ;
이렇게 변환할 때,
기본형(int)였던 1, 2, 3 등의 값들이
new Integer(1)
, new Integer(2)
, new Integer(3)
으로
즉, 참조형으로 바뀌어서 저장 ( 오토 박싱 )이 된다.
그렇기 때문에
이런 오토박싱 & 언박싱으로 인한 비효율적인 처리를 방지하기 위해
【기본형 스트림】IntStream
, LongStream
, DoubleStream
...등등
을 제공한다.
"""
데이터 소스가 기본형일 때
"""
이 " 기본형 스트림 "을 사용하면
처리 효율 측면에서도 더욱 효율적이고
타입에 맞춰 적용되어야 하는 Stream<T>
보다
이미 타입을 알고 최적화되어 있는 기본형 스트림에는
해당 기본형 값을 처리하는데 유용한 메서드들이 포함되어 있다. (ex. sum()
, average()
..등)
: 멀티 쓰레드(Multi-Thread)를 통해 병렬 작업 처리를 수행하기 쉽다.
함수형 프로그래밍이 주목받기 시작한 것이 "빅데이터" 즉, 대량의 데이터에 대한 작업에서 멀티 쓰레드를 통한 빠른 처리 덕분이라 했는데
이러한 기능을 수행하기 위해 사용되는 것이 병렬 스트림이라고 할 수 있다.
Java 자체 내부적으로 fork&join 프레임 웍을 이용해서
자동적으로 연산을 병렬로 수행한다.
사용자는 그저 Stream에
parallel()
이라는 메서드를 호출해서 지시하기만 하면 된다.
* parallel()
취소 : sequential()
일단 기본적으로 Stream은 병렬 스트림이 아니기 때문에
sequential()
은 parallel()
을 사용하고 취소할 때만 사용하면 되고
알고 있어야할 것은
위 메서드 [sequential()
/ parallel()
]는
새로운 스트림을 만드는 것이 아니라
속성을 변경하는 것이다.