[Java] Stream API

김나우·2022년 6월 2일
0

Java

목록 보기
15/15

출처 : https://mangkyu.tistory.com/112?category=872426

1. Stream API


Stream API란?

Java는 객체지향 언어이기 때문에 기본적으로 함수형 프로그래밍이 불가능하다.

하지만 JDK8부터 Stream API와 람다식, 함수형 인터페이스 등을 지원하면서 Java를 이용해 함수형 프로그래밍을 할 수 있는 API들을 제공해주고 있다.

그 중에서 Stream API는 데이터를 추상화하고, 처리하는데 자주 사용되는 함수들을 정의 해두었다.

데이터를 추상화하였다는 것은 데이터의 종류에 상관 없이 같은 방식으로 데이터를 처리할 수 있다는 것을 의미하며, 그에 따라 재사용성을 높일 수 있다.

예를 들어 주어진 배열이나 리스트의 데이터를 정렬된 상태로 출력하고자 한다. Stream API를 사용하지 않는 경우 다음과 같이 코드를 작성할 수 있다.

Stream API 사용 전

//Stream 사용 전
String[] nameArr = {"name1", "name2", "name3", "name4"};
List<String> nameList = Arrays.asList(nameArr);

//원본의 데이터가 직렵 정렬됨
Arrays.sort(nameArr);
Collections.sort(nameList);

for(String str : nameArr){
	System.out.println(str);
}

for(String str : nameList){
	System.out.println(str);
}

Stream API 사용 후

//Stream 사용 후
String[] nameArr = {"name1", "name2", "name3", "name4"};
List<String> nameList = Arrays.asList(nameArr);

//원본의 데이터가 아닌 별도의 Stream을 생성
Stream<String> nameStream = nameList.stream();
Stream<String> arrayStream = Arrays.stream(nameArr);

//복사된 데이터를 정렬하여 출력함
nameStream.sorted().forEach(System.out::println);
arrayStream.sorted().forEach(System.out::println);

이처럼 Stream API를 활용하면 코드의 라인수도 줄이고, 가독성도 높일 수 있다.

이러한 장점을 지닌 Stream API에 대해 자세히 살펴보자.


Stream API의 특징

  1. 원본의 데이터를 변경하지 않는다.
  2. 일회용이다.
  3. 내부 반복으로 작업을 처리한다.

1. 원본의 데이터를 변경하지 않는다

Stream API는 원본의 데이터를 조회하여 원본의 데이터가 아닌 별도의 요소들로 Stream을 생성한다.

그렇기 때문에 원본의 데이터로부터 읽기만 할 뿐이며, 정렬이나 필터링 등의 작업은 별도의 Stream 요소들에서 처리가 된다.

List<String> sortedList = nameStream.sorted().collect(Collectors.toList());

2. Stream은 일회용이다

Stream API는 일회용이기 때문에 한번 사용이 끝나면 재사용이 불가능하다.

Stream이 또 필요한 경우에는 Stream을 다시 생성해주어야 한다.

만약 닫힌 Stream을 다시 사용한다면 IllegalStateException이 발생하게 된다.

userStream.sorted().forEach(System.out::println);

long count = userStream.count();

//에러
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
	at java.util.stream.LongPipeline.<init>(LongPipeline.java:91)
	at java.util.stream.LongPipeline$StatelessOp.<init>(LongPipeline.java:574)
	at java.util.stream.ReferencePipeline$5.<init>(ReferencePipeline.java:221)
	at java.util.stream.ReferencePipeline.mapToLong(ReferencePipeline.java:220)
	at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
	at main.java.lambda.Lambda.main(Lambda.java:19)

3. 내부 반복으로 작업을 처리한다.

Stream을 이용하면 코드가 간결해지는 이유 중 하나는 내부 반복 때문이다.

기존에는 반복문을 사용하기 위해서 for, while등과 같은 문법을 사용해야 했지만, Stream에서는

그러한 반복 문법을 메소드 내부에 숨기고 있기 때문에, 보다 간결한 코드의 작성이 가능하다.

//반복문이 forEach라는 함수 내부에 숨겨져 있다.
userStream.sorted().forEach(System.out::println);


Stream API 연산 종류


Steam은 데이터를 처리하기 위해 다양한 연산들을 지원한다. Stream이 제공하는 연산을 이용하면

복잡한 작업들을 간단히 처리할 수 있는데, 스트림에 대한 연산은 크게 생성하기, 가공하기, 결과 만들기 3단계로 나눌 수 있다.

  1. 생성하기
  2. 가공하기
  3. 결과 만들기

생성하기

  • Stream 객체를 생성하는 단계
  • Stream은 재사용이 불가능하므로, 닫히면 다시 생성해주어야 한다

Steam연산을 하기 위해서는 먼저 Steam 객체를 생성해주어야 한다. 배열, 컬렉션, 임의의 수,

파일 등 거의 모든 것을 가지고 스트림을 생성할 수 있다. 주의할 점은 연산이 끝나면 Steam이

닫히기 때문에, Stream이 닫혔을 경우 다시 Steam을 생성해야 한다는 것이다.

가공하기

  • 원본의 데이터를 별도의 데이터로 가공하기 위한 중간 연산
  • 연산 결과를 Steam으로 다시 반환하기 때문에 연속해서 중간 연산을 이어갈 수 있다.

가공하기 단계는 원본의 데이터를 별도의 데이터로 가공하기 위한 중간 연산의 단계이다.

어떤 객체의 Steam을 원하는 형태로 처리할 수 있으며, 중간 연산의 반환값은 Stream이기

때문에 필요한 만큼 중간 연산을 연결하여 사용할 수 있다.

결과 만들기

  • 가공된 데이터로부터 원하는 결과를 만들기 위한 최종 연산
  • Stream의 요소들을 소모하면서 연산이 수행되기 때문에 1번만 처리가능하다.

[Steram 연산 예시]

List<String> myList = Arrays.asList("a1", "a2", "b1", "b2", "c2", "c1");

        myList
                .stream()                       //생성
                .filter(s -> s.startsWith("c")) //가공
                .map(String::toUpperCase)       //가공
                .sorted()                       //가공
                .count();                       //결과 만들기
profile
안녕하세요

0개의 댓글