Study/Java

Java: Stream 이해하고 적용하기

devyoseph 2021. 11. 12. 00:42

Stream

Java 8부터 람다 표현식(lamda expression)을 사용할 수 있게 되었고

같은 시기에 Collection의 Stream 클래스를 사용할 수 있게 되었다

 

Stream 동작

Collection(ArrayList, Set, Map 등), 배열, 가변 매개변수, 특정 타입 난수, 람다 표현식, 파일, 빈 스트림 등

다양한 범위의 데이터를 가공할 수 있다. Iterator과 비슷한 방식으로 동작한다.

데이터를 복사해서 가져온 뒤 사용자가 원하는 연산을 수행한다.

 

원본 자료 → Stream 데이터 형식으로 복사 → 내부 연산 → 출력 (데이터 소모)

*이미 원본을 복사한 자료이기 때문에 최종 연산 이후 내부 자료들이 소모된다(재사용X)

즉, Stream은 복사한 원본을 이용해 사용자가 원하는 방식으로 가공해주는 역할을 한다.

 

Stream 사용

[ 생성 → 중개 연산 → 최종연산 ]의 단계로 사용된다

개념이 복잡해보이지만 코드는 매우 간결하다. 연산 메소드와 출력 메소드를 연속해서 사용할 수 있기 때문이다.

 

생성

원본을 먼저 Stream 데이터로 복사해주어야 한다

주로 사용되는 배열이나 ArrayList .stream() 메소드를 이용해 복사한다

//숫자 배열 Stream 생성
	int[] arr = {3, 4, 5, 2, 1};
	IntStream stream = Arrays.stream(arr); //Arrays 내부 .stream() 메소드
	
    stream.sorted().forEach(System.out::print); //정렬 후 바로 출력하기
	//12345
    
//문자 배열 등 Wrapper Class
	String[] arr = {"스","트","림"};
	Stream<String> stream = Arrays.stream(arr);//<Double>, <Integer> 등 사용

	stream.sorted().forEach(System.out::print);
	//림스트
	
//배열 특정 부분
	String[] arr = {"스","트","림"};
	Stream<String> stream = Arrays.stream(arr,1,2);

	stream.sorted().forEach(System.out::print);
    //트


//컬렉션
	ArrayList<Integer> list = new ArrayList<Integer>();
	Stream<Integer> stream = list.stream(); //컬렉션 내부 .stream() 사용

그 외 메소드를 이용해 수열을 만들거나 파일의 라인을 복사할 수도 있다

//가변 매개변수
	Stream<Integer> stream = Stream.of(1,3,4,8,2); //Stream 내부 of 메소드
	stream.sorted(Comparator.reverseOrder()).forEach(System.out::print); //역순 정렬
    //출력: 84321

//연속 정수
	IntStream stream2 = IntStream.range(1,5);
	stream2.forEach(System.out::print);
    
//람다 표현식
	Stream<Integer> stream2 = Stream.iterate(1, x -> x + 1);  //초항 1에서 1을 계속 더한 값들을 출력
	stream2.forEach(System.out::print);
    //123456....
    
//파일
	Stream<Integer> stream2 = Files.lines(path); // .line() 메소드를 사용해 라인 단위 접근
    
//빈 스트림
	Stream<Object> stream = Stream.empty();
    //Stream 클래스 .count()메소드를 사용해 개수를 호출하면 0이 출력

 

중개 연산, 최종 연산

중개연산을 통해 원하는 데이터를 가공하고 최종연산을 통해 원하는 방식으로 가져온다

사용법: Stream명.중개연산.최종연산

ex) stream.filter(n -> n<50).forEach(System.out::print) //원소 중 50보다 작은 수를 출력합니다

Stream 연산
중개 연산 최종 연산
.filter(x -> x<500) 원하는 조건으로 걸러낸다 .forEach(e -> System.out.print(e));
.forEach(System.out::print); //출력
.distinct() 중복되는 값을 걸러낸다
.map(x -> x.upperCase()) 함수를 통해 값들을 변경 reduce( (a,b) -> a+b ); 인접한 원소들끼리 연산을 수행
.flatmap() 원소들을 쪼개서 stream에 저장 .findFirst() 첫 번째 원소 출력
.limit(n) 0부터 n까지 원소를 대상으로 함 .findAny() 첫 번째 원소 출력
.skip(n) 0부터 n까지 원소를 제외한 대상 .anyMatch( n -> n<50 ) 조건을 만족하는 원소가 있는지
.sorted() 모든 원소를 오름차순 정렬 .allMatch( n -> n<100) 조건을 모든 원소가 만족하는지
.sorted(Comparator.reverseOrder()) : 내림차순 정렬 .count() 원소 개수
.peek() stream의 연산 결과 확인 .max.getAsInt() 최대값
  .min.getAsint() 최소값
.sum() 원소의 합
.average() 원소의 평균
.collect( ) 리스트 변환

 

Stream 장점

내부 반복자를 사용하므로 병렬 처리가 쉽다. 개발자는 요소 처리 코드에만 집중할 수 있고

요소들을 분배해 병렬 처리 작업을 할 수 있다

외부 반복자(external iterator)
내부 반복자(internal iterator)
개발자가 코드로 직접 컬렉션 요소를 반복해서 요청하고 가져오는
코드 패턴
컬렉션 내부에서 요소들을 반복시키고 개발자는 요소당 처리해야할
코드만 제공하는 코드 패턴

병렬(parallel)처리: 한가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리한 후, 서브 작업들의 결과들을 최종 결합하는 방법

 

Stream 예시

Stream은 원본을 변경하지 않으면서 코드가 간결해진다는 장점이 있다

Stream을 사용해 1부터 100까지 수 중 3의 배수만 나열하기

import java.util.stream.*;
public class Main {
	public static void main(String[] args) {
		IntStream stream = IntStream.range(1, 100);
		stream.filter(x->x%3==0).forEach(e->System.out.print(e+" "));
 }}