[Java] Stream

예원·2022년 12월 19일
0

Java 글 모아보기

목록 보기
2/6

Stream이란?

Java 8에서 추가한 스트림(Stream)은 람다를 활용할 수 있는 기술 중 하나이다.
Java 8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach 문을 돌면서 요소 하나씩 꺼내서 다루는 방법이었다.
스트림도 for-loop와 비슷한 역할을 하지만 람다식으로 요소 처리 코드를 제공하여 코드가 좀 더 간결하게 할 수 있으며, 내부 반복자를 사용하므로 병렬 처리가 쉽다는 차이점이 있다.

스트림에 대한 내용은 크게 세 가지로 나눌 수 있다.

public List<Member> findAdultAsName(List<Member> members, int count) {
  return members.stream()    // 생성하기
  .filter(member -> member.isAdult())    // 중간 연산
  .limit(count)    // 중간 연산
  .map(Member::getName)    // 중간 연산
  .collect(Collectors.toList());    // 최종 연산
  }
  1. 생성하기 : 컬렉션으로부터 스트림 객체 얻기
  2. 가공하기 : 중간 연산을 통해 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만든다. 여러번 사용될 수 있다.
  3. 결과 만들기 : 최종 연산을 통해 연산이 끝난 값을 컬렉션 자료구조로 변경한다.

스트림 사용하기

스트림의 많은 기능은 공식문서를 참고할 수 있으며, 이 글에서는 간단한 사용법에 대해서만 알아본다.

생성하기

stream() 매서드를 사용해 컬렉션으로부터 스트림 객체를 얻을 수 있다.

List<String> member = new ArrayList<>();
Stream<String> stream = member.stream();

중간 연산

스트림의 중간 연산에는 람다식이 사용된다.
람다식은 익명함수, 즉 이름이 존재하지 않는 함수(메서드)를 뜻한다.
람다식을 사용할 경우 객체 생성 없이 메서드를 호출하듯이 바로 사용 가능하다.

중간 연산에 사용되는 메서드는 다양하며, 메서드들은 순서에 상관없이 중간 연산에서 계속 사용할 수 있다.

filter

  • 조건문과 비슷하며, 참과 거짓을 판단한다.
.filter(userNumber -> userNumber.equals(targetNumber))

limit

  • 원하는 개수만큼만 반환한다.
.limit(6)

distinct

  • 중복된 요소를 제거한다.
.distinct()

anyMatch

  • 일치하는 값이 있는지 확인한다.
  • 하나라도 일치하면 true를 반환한다.
.anyMatch(userNumber -> userNumber.equals(targetNumber)); 

map

  • 스트림 요소를 다른 타입으로 변경한다.
.map(member -> member.getName())  // member Stream -> member.name Stream으로 변경

.map(Member::getName) 

최종 연산

중간 연산이 끝나거나 혹은 사용하지 않을 때, 최종적으로 스트림의 결과를 지정해주는 단계이다.
컬렉션으로 만들거나, 계산을 하면서 마무리한다.

collect

  • List, Set, Queue, Map으로 만들 수 있다.
.collect(Collectors.toList());  // 리스트로 만든다.

.collect(Collectors.toSet());  // Set으로 만든다.

reduce

  • 사칙연산을 하며 마무리한다.
// 시작과 수행할 연산을 파라미터로 한다.
.reduce(0, Integer::sum);  // 합은 0을 시작으로 하며, sum 메서드를 통해 계산한다.

.reduce(1.0, (a, b) -> a * b);  // 곱은 double 타입으로 1.0으로 시작하며, 람다식을 이용한 곱 계산을 한다.

findAny/findFirst

  • 스트림에서 한 가지의 값만 골라낼 수 있다.
  • .findAny() : 최초로 해당하는 값이 나오면 그 요소를 반환한다.
  • .findFirst() : 모든 요소를 탐색 한 후 그 중에서 첫 번째 요소를 반환한다.
filter(userNumber -> userNumber.equals(targetNumber))
.findAny()  // 최초로 userNumber가 targetNumber와 같은 값이 나오면 그 요소를 반환한다.

filter(userNumber -> userNumber.equals(targetNumber))
.findFirst()  // 모든 요소를 탐색해서 userNumber가 targetNumber와 같은 값을 찾고
			  //그중에 첫 번째 요소를 반환한다.      

컬렉션과 차이점

스트림과 컬렉션은 모두 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스를 제공한다.
그렇다면 스트림과 컬렉션의 차이점은 뭘까?

1. 데이터 계산 시점

데이터를 언제 계산하느냐 차이점이 있다.

  • 컬렉션 : 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다.
  • 스트림 : 요청할때만 요소를 계산하는 고정된 자료구조이다.

스트림은 사용자가 요청하는 값만 추출할 수 있는 특정이 있어 컬렉션보다 프로그래밍에 장점이 있다.

2. 반복의 일회성

컬렉션은 같은 소스에 대해 여러번 반복 처리를 할 수 있지만 스트림은 단 한번의 반복문을 처리할 수 있다.

Stream<String> stream = member.stream();
stream.forEach(System.out::println); // 정상
stream.forEach(System.out::println); // IllegalStateException 발생

스트림은 한번 소비한 요소에 대해서는 접근할 수 없기 때문이다.
만약 한번 소비한 후 다시 호출한다면 java.lang.IllegalStateException 에러가 발생한다.

3. 외부반복과 내부반복

  • 외부반복 : foreach 문법을 사용하여 사용자가 반복문을 직접 명시해야 한다.
List<String> names = new ArrayList<>();
for (User u : users) {
  names.add(users.getName());
}
  • 내부반복 : 스트림은 라이브러리를 사용한다.
List<String> names = users.stream()
	.map(User::getName)
    .collect(toList());

스트림은 별도의 반복자 없이 반복문을 처리할 수있다.
스트림이 사용하는 내부반복은 작업을 병렬로 처리할 수 있고 더 최적화된 다양한 순서로 처리할 수 있다.


Stream은 왜 for-loop보다 느릴까?

꼭 Strean API를 사용하는 것이 좋을까? 오히려 Stream이 for-loop보다 느리다고 한다.
왜 느릴까?

  1. for-loop는 단순한 인덱스 기반 메모리 접근이다.

    • 따라서 for-loop의 경우 오버헤드가 없다.
  2. for-loop는 컴파일러 관점에서 최적화 할 수 있다.

    • for-loop는 오랫동안 사용되어 왔기 때문에 컴파일러가 for-loop에 대한 처리가 되어있어 미리 최적화 시킬 수 있다.
    • 하지만 stream은 Java 8부터 사용됬기 때문에 for-loop 만큼 정교한 최적화를 수행할 수 없다.

그럼 왜 Stream을 사용하는 걸까?

이것은 결국 개발자 역량에 따른 문제이다.

그럼에도 굳이 이유를 뽑자면 가독성이 좋기 때문이다.
Stream API에 포함되어 있는 여러 함수들을 이용하여 코드를 간결하게 할 수 있다.

하지만 가독성이 좋다는 것은 모든 개발자가 Stream에 대해 알고 있을 경우이다..

결론은

상황에 따라 Stream APIfor-loop를 적절히 사용하는 것이 중요하다.


reference

https://tecoble.techcourse.co.kr/post/2021-05-23-stream-api-basic/
https://velog.io/@guns95/JAVA-8-STREAM
https://ksr930.tistory.com/237
https://coding-factory.tistory.com/574
https://futurecreator.github.io/2018/08/26/java-8-streams/
https://velog.io/@gmtmoney2357/자바-스트림Stream
https://jypthemiracle.medium.com/java-stream-api는-왜-for-loop보다-느릴까-50dec4b9974b

0개의 댓글