Java - 스트림

<이것이 자바다>를 읽으며 정리

Posted by Yan on June 26, 2021

Stream

자바 8부터 추가된 컬렉션의 저장요소를 하나씩 참조해서 람다식(functional style)으로 처리할 수 있도록 해주는 반복자

iterator -> stream

iterator 반복자를 사용했을 때

1
2
3
4
5
6
List<String> list = Arrays.asList("김", "나", "박", "이");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
    String name = iterator.next();
    System.out.println(name);
}

stream을 사용했을 때

1
2
3
List<String> list = Arrays.asList("김", "나", "박", "이");
Stream<String> stream = list.stream();
stream.forEach(name -> System.out.println(name));

스트림의 특징

iterator와 차이점

  • 람다식으로 요소 처리 코드를 제공한다
  • 내부 반복자를 사용해 병렬 처리가 쉽다
  • 중간 처리와 최종 처리 작업을 수행한다

1. 람다식으로 요소 처리 코드를 제공

  • 스트림에서는 람다식 또는 메소드 참조를 이용해 ㅇ내용을 매개변수로 전달할 수 있다
1
2
3
stream.forEach(s -> {
    System.out.println(s.getName()); // list의 각각의 item을 s로 부르고 매개변수로 전달한 것
})

2. 내부 반복자를 사용해 병렬 처리

  • 외부 반복자external iterator : 개발자가 코드로 직접 컬렉션 요소를 반복해서 가져오는 코드 패턴
    • index를 이용하는 for문, while문 등

iterator (출처: https://stackoverflow.com/questions/224648/external-iterator-vs-internal-iterator)

  • 내부 반복자의 장점
    • 개발자가 컬렉션 요소 반복 처리를 직접 작성하지 않아도 됨(스트림이 알아서 하니까)
    • 요소들의 반복 순서를 변경하거나, 멀티코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있도록 도와주기 때문에 하나씩 처리하는 순차적 외부 반복자보다 효율적으로 요소를 반복시킬 수 있다
  • 병렬 처리 parallel : 한가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것
    • 런타임시 하나의 작업을 서브작업으로 자동으로 나눈 뒤, 서브 작업 결과를 자동으로 결합해서 최종 결과물을 생성한다

3. 중간 처리와 최종 처리 작업 수행

  • 스트림은 컬렉션 요소에 대해 중간 처리와 최종 처리를 수행할 수 있다
  • 중간 처리 : 매핑, 필터링, 정렬 수행
  • 최종 처리 : 반복, 카운팅, 평균, 총합

stream (출처: https://strange-developer.tistory.com/15)

스트림의 종류

  • BaseStream 인터페이스에 모든 스트림에서 사용할 수 있는 공통 메소드들이 정의되어있다.
  • 하위 스트림으로 Stream, IntStream, LongStream, DoubleStream이 있다.
  • Stream : 객체 요소를 처리하는 스트림
  • IntStream : int요소를 처리하는 스트림

메소드 예시

리턴타입 메소드(매개변수) 소스
Stream<T> - java.util.Collection.stream()
- java.util.Collection.parallelStream()
컬렉션
Stream<Path> - Files.find(Path, int, BiPredicate, FileVisitOption)
- Files.list(Path)
디렉토리
Stream<String> - Files.lines(Path, Charset)
- BufferedReader.lines()
파일

컬렉션으로부터 스트림 얻기

  • 리스트이름.stream()

배열로부터 스트림 얻기

  • Arrays.stream(배열이름)

숫자 범위로부터 스트림 얻기

  • IntStream.rangeClosed(1, 100).forEach(a -> sum += a) : 1부터 100까지 합 구하기

파일로부터 스트림 얻기

  • Path path = Paths.get("src/example.txt") : 파일 경로 정보를 가지고 있는 Path객체 생성
  • Stream<String> stream = Files.lines(path, Charset.defaultCharset()) : 운영체제의 기본 문자셋
  • BufferedReader의 lines()메소드 이용
1
2
3
4
5
File file = path.toFile();
FileReader fileReader = new FileReader(file);
BufferedREader br = new BufferedREader(fileReader);
stream = br.lines();
stream.forEach(System.out :: println);

디렉토리로부터 스트림 얻기

  • Files의 정적 메소드 list()이용해 디렉토리의 내용을 스트림으로 읽고 콘솔에 출력하는 예제
1
2
3
4
5
Path path = Paths.get("C:/Users/src");
Stream<Path> stream = Files.list(path);
stream.forEach(p -> System.out.println(p.getFileName()));
// P : 서브 디렉토리 또는 파일에 해당하는 Path 객체
// p.getFileName() : 서브 디렉토리 이름 또는 파일 이름 리턴

스트림 파이프라인

  • 리덕션Reduction : 대량의 데이터를 가공해서 축소하는 것
  • 리덕션의 결과물: 데이터의 합계, 평균값, 카운팅, 최대값, 최소값
  • 컬렉션 요소를 리덕션 결과물로 바로 집계할 수 없을 경우, 집계하기 좋도록 필터링, 매핑, 정렬, 그룹핑 등의 중간 처리가 필요

중간 처리와 최종 처리

  • 파이프라인piplelines : 여러 개의 스트림이 연결되어 있는 구조
  • 스트림은 중간처리와 최종처리를 파이프라인으로 해결한다
  • 파이프라인에서 최종 처리를 제외하고는 모두 중간 처리 스트림이다
  • 중간 스트림이 생성될 때 요소들이 바로 중간처리 (필터링, 매핑, 정렬)되는 것이 아니라
    최종 처리가 시작되기 전까지 중간 처리는 지연된다(lazy)
  • 최종 처리가 시작되면 비로소 컬렉션의 요소가 하나씩 중간 스트림에서 처리되고 최종처리까지 오게 된다