📌스트림이란?
많은 수의 데이터를 다룰 때, 컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위해 코드를 작성하면 너무 길고, 재사용성도 떨어진다. 이런 문제를 해결하기 위해 만든 것이 스트림(stream)이다. 스트림은 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다. 데이터소스를 추상화하였다는 것은, 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재사용성이 높아진다는 것을 의미한다.
즉, 스트림(Stream)은 컬렉션(배열 포함)의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.
간단한 예를 하나 보여주도록 하겠다. 뒤에서 자세히 설명할 것이니 그냥 보기만 해도 된다.
//기존 Iterator
List<String> list = Arrays.asList("사과", "바나나", "코코넛");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String name = iterator.next();
System.out.println(name);
}
//Stream 사용
List<String> list = Arrays.asList("사과", "바나나", "코코넛");
Stream<String> name = list.stream();
stream.forEach( name -> System.out.println(name) );
위 처럼 Stream이 제공하는 대부분의 요소 처리 메소드는 함수적 엔터페이스 매개 타입을 가지기 때문에 람다식 또는 메소드 참조를 이용해서 요소 처리 내용을 매개값으로 전달할 수 있다.
내부 반복자를 사용하므로 병렬 처리가 쉽다.
스트림을 이용한 작업이 간결할 수 있는 비결중의 하나가 바로 '내부 반복'이다. 내부반복이라는 것은 반복문을 메서드의 내부에 숨길 수 있다는 것을 의미한다. forEach( )는 스트림에 정의된 메서드 중의 하나로 매개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용한다.
외부 반복자란 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴을 말한다. index를 이용하는 for문 그리고 Iterator를 이용하는 while문은 모두 외부 반복자를 이용하는 것이다.
반면에 내부 반복자는 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공하는 코드 패턴을 말한다. 내부 반복자를 사용해서 얻는 이점은 컬렉션 내부에서 어떻게 요소를 반복시킬 것인가는 컬렉션에 맡겨두고, 개발자는 요소 처리 코드에만 집중할 수 있는것이다. 내부 반복자는 멀티 코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있게 도와주기 때문에 하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복시킬 수 있다.
스트림의 연산 (중간 처리 연산, 최종 처리 연산)
스트림이 제공하는 연산은 중간 연산과 최종 연산으로 분류할 수 있는데,
중간 연산은 연산결과를 스트림으로 반환하기 때문에 중간 연산을 연속해서 할 수 있음
최종 연산은 스트림의 요소를 소모하면서 연산을 수행하므로 단 한번만 연산이 가능하다.
중간 연산에서는 매핑, 필터링, 정렬을 수행하고
최종 연산에서는 반복, 카운팅, 평균, 총합 등의 집계 처리를 수행한다.
stream . distinct( ) . limit(5) . sorted( ) . forEach(System.out::println)
중간연산 중간연산 중간연산 최종연산
※System.out :: println 는 s -> System.out.println(s) 와 동일하다.
모든 중간 연산의 결과는 스트림이지만, 연산 전의 스트림과 같은 것은 아니다. 위의 문장과 달리 모든 스트림 연산을 나누어 쓰면 다음과 같다. 각 연산의 반환타입을 살펴보자
중간 처리 연산
필터링 | distinct() | 중복 제거 .distinct() |
filter(...) | 필터링 .filter(n->n.startsWith("신")) 신으로 시작 |
|
매핑 | flatMap(...) | 요소를 대체하는 복수 개의 요소들로 구성된 새로운 스트림을 리턴한다. |
flatMapToXXX(...) | ||
map(...) | 요소를 대체하는 요소로 구성된 새로운 스트림을 리턴한다. | |
mapToXXX(...) | ||
asXXXStream(...) | 요소를 XXX 타입 변환해서 XXXStream생성한다. | |
boxed() | int,long,double 요소를 Integer,Long,Double 요소로 박싱해서 Stream 생성 | |
정렬 | sorted(...) | 정렬 .sorted() .sorted( (a,b) -> {...} ) .sorted( Comparator.reverseOrder() ); |
루핑 | peek(...) | 요소 전체를 반복한다. 기능은 forEach()와 같지만, 중간 처리 메소드라는 점에서 동작 방식이 다르다. ex) .filter(a->a%2==) .peek(a->System.out.println(a)) .sum() |
최종 처리 연산
매칭 | allMatch(...) | 모든 요소들이 매개값으로 주어진 Predicate의 조건을 만족하는지 조사 |
anyMatch(...) | 최소한 한 개의 요소가 매개값으로 주어진 Predicate의 조건을 만족하는지 조사 | |
noneMatch(...) | 모든 요소들이 매개값으로 주어진 Predicated의 조건을 만족하지 않는지 조사 | |
집계 | count() | |
findFirst() | ||
max(...) | ||
min(...) | ||
average() | ||
reduce(...) | 커스텀 집계 프로그램화해서 다양한 집계 결과물을 만들 수 있도록 reduce() 메소드 제공 |
|
sum() | ||
루핑 | forEach(...) | 요소 전체를 반복한다. 최종 처리 메소드이다. |
수집 | collect(...) | 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴한다. .collect(Collectors.toList()); |
스트림 얻기
- 컬렉션으로부터 스트림 얻기
Stream<String> stream = list.stream(); - 배열로부터 스트림 얻기
Stream<String> strStream = Array.stream(strArray); - 숫자 범위로부터 스트림 얻기
IntStream stream = IntStream.rangeClosed(1, 100); //1부터 100까지 순차적으로 제공하는 IntStream을 리턴한다.
IntStream stream = IntStream.range(1,100); //1부터 99까지 순차적으로 제공하는 IntStream을 리턴한다. - 파일로부터 스트림 얻기
Path path = Paths.get("~~~~/파일명"); //파일의 경로 정보를 가지고 있는 Path 객체 생성
Stream<String> stream = Files.lines(path, Charset.defaultCharset()); //파일의 내용을 lines()로 읽어와서 담음
stream.forEach(System.out :: println); - 디렉토리로부터 스트림 얻기 (서브 디렉토리 또는 파일 목록)
Path path = Paths.get("~~~~\디렉터리 경로");
Stream<Path> stream = File.list(path);
stream.forEach( p -> System.out.println(p.getFileName()) );
스트림은 데이터 소스를 변경하지 않는다.
스트림은 데이터 소스로 부터 데이터를 읽기만 할 뿐, 데이터 소스를 변경하지 않는다는 차이가 있다. 필요하다면, 정렬된 결과를 컬렉션이나 배열에 담아서 반환할 수도 있다.
//정렬된 결과를 새로운 List에 담아서 반환한다.
List<Stinrg> sortedList = strStream2.sorted().collect(Collectors.toList());
스트림은 Iterator처럼 일회용이다. (필요하면 다시 스트림을 생성해야 한다.)
스트림은 Iterator처럼 일회용이다. Iterator로 컬렉션의 요소를 모두 읽고 나면 다시 사용할 수 없는 거처럼, 스트림도 한번 사용하면 닫혀서 다시 사용할 수 없다.
strStream1.sorted().forEach(System.out::println);
int numOfStr = strStream1.count(); //error. 스트림이 이미 닫혔음.
'Java' 카테고리의 다른 글
[Java] TCP 네트워킹 - (4)스레드 병렬 처리 (0) | 2022.06.06 |
---|---|
[Java] IO 기반 입출력 - (1)입력 스트림과 출력 스트림 (0) | 2022.06.05 |
BufferedReader / BufferWriter (0) | 2022.04.01 |
[Java] 람다식 (Lambda expression) (0) | 2021.08.07 |
[Java]Java.lang 패키지 (Object, System, Class, String, String, Pattern, Arrays, Math, Wrapper) (0) | 2021.08.04 |