본문 바로가기
Develop/Java

[Java Stream] 자바 스트림

by 연로그 2021. 3. 17.
반응형

스트림; Stream

  • 데이터의 흐름
  • java 8에서 추가한 람다를 활용할 수 있는 기술 중 하나
  • 배열 / 컬렉션 인스턴스에 함수 여러 개를 조합해 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다.
  • 한 번 종료 작업을 한 스트림에 대해서는 재사용 불가
  • 병렬 처리* 가능 -> 여러 스레드가 작업

      * 병렬 처리: 하나의 작업을 둘 이상으로 나눠 동시에 진행하는 것

 

스트림 구조

  1. 생성하기: 스트림 인스턴스 생성
  2. 가공하기: filtering, mapping 등 원하는 결과 만들기
  3. 결과 만들기: 최종 결과 만들어내는 작업

-> 객체.스트림생성().가공().결과만들기(); 식이다.

 

( 이렇게 연계할 수 있는 방식을 파이프라인이라 부른다고도 한다. )

 

 


1. 생성하기

 

주로 Collection, Arrays에서 쓰이고, I/O resources(ex: File), Generators, Stream ranges, Pattern 등에서도 쓰인다.

//Collection에서 스트림 생성
List<String> names = Arrays.asList("jeong", "pro", "jdk", "java");
names.stream(); 

//배열로 스트림 생성
Double[] dArray = {3.1, 3.2, 3.3};
Arrays.stream(dArray);
 
// 스트림 직접 생성
Stream<Integer> str = Stream.of(1,2); 

 

 


2. 가공하기

filter

  • 스트림 내 요소들을 하나씩 평가해 걸러내는 작업
  • ex: "b"가 포함되어 있는 요소들만 스트림 형태로 반환
List<String> names = Arrays.asList("abc","bcd","cde","def");
Stream<String> st = names.stream().filter(x -> x.contains("b"));

 

map

  • 스트림 내 요소들을 하나씩 연산
  • ex: 스트림 내 String의 toUpperCase 메소드 실행기술 중 하나
List<String> names = Arrays.asList("a","b","c","d");
Stream<String> st = names.stream().map(String::toUpperCase);

 

sorted

  • 요소들 정렬
  • 디폴트: 오름차순
List<String> lang = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");

lang = lang.stream()
	  .sorted() // 오름차순
	  .collect(Collectors.toList());

lang = lang.stream()
	  .sorted(Comparator.reverseOrder()) // 내림차순
	  .collect(Collectors.toList());
      
lang.stream()
	  .sorted(Comparator.comparingInt(String::length)) // 글자수 기준, 오름차순
	  .collect(Collectors.toList());

lang.stream()
	  .sorted((s1, s2) -> s2.length() - s1.length()) // 글자수 기준, 내림차순
	  .collect(Collectors.toList());

 

peek

  • 스트림 내 각 요소들로 특정 연산 수행
  • 특정 결과 반환 X
  • ex: 계산 과정 보고싶은 경우
int sum = IntStream.of(1, 3, 5, 7, 9)
	  .peek(System.out::println)
	  .sum();

 

 


3. 결과 만들기

 

count, min, max, sum

  • 요소들에 대한 개수, 최소값, 최대값, 합계
  • ex: 값이 1인 요소의 개수
List<Integer> ages = Arrays.asList(1, 2, 3);
ages.stream().map(x->x==1).count();

 

reduce

  • 누적된 값을 계산
  • ex: 요소들의 합 구하기. x+y 결과는 다음 스트림의 x, 다음 요소는 y가 되어 계속 누적된다. 
List<Integer> ages = Arrays.asList(1, 2, 3);

ages.stream().reduce((x,y) -> x+y).get(); // 1+2+3 = 6
ages.stream().reduce((a,b) -> { return Integer.sum(a,b); });
ages.stream().reduce(0, Integer::sum); // 0은 초기값. 10으로 할 경우 10+1+2+3=16이 됨

 

forEach

  • 요소를 돌며 실행되는 최종 작업
  • peek과 유사하지만 peek은 중간 작업임
names.stream().forEach(System.out::println);

 

collect

  • Collector 타입의 인자를 받아서 처리
  • 예제 외에도 Collectors를 활용한 예는 많지만 생략하겠다. (포스팅 아래에 참조1 링크에서 자세히 확인 가능)
productList.stream()
    .map(Product::getName)
    .collect(Collectors.toList()); // 리스트로 결과 가져오기
    
productList.stream()
  .map(Product::getName)
  .collect(Collectors.joining()); // 스트림 작업 결과를 하나의 String으로 이어붙이기
  
productList.stream()
  .map(Product::getName)
  .collect(Collectors.joining(", ", "<", ">")); //<요소1, 요소2, 요소3>
  
productList.stream()
  .collect(Collectors.averagingInt(Product::getAmount)); // 숫자 값의 평균

 

noneMatch, anyMatch, allMatch

  • noneMatch: 모든 요소들이 조건 불만족 하는지
  • anyMatch : 하나라도 조건 만족하는지
  • allMatch   : 모든 요소들이 조건 만족하는지
  • return boolean값
boolean anyMatch = names.stream()
  .anyMatch(name -> name.contains("a"));

 

 


스트림 동작 순서

대략적인 스트림 연산들을 살펴보았으니 이제 동작 순서를 살펴보자.

다음 코드에 대해 출력 결과를 생각해보자.

Arrays.stream(new String[]{"c", "python", "java"})
        .filter(word -> {
            System.out.println("Call filter method: " + word);
            return word.length() > 3;
        })
        .map(word -> {
            System.out.println("Call map method: " + word);
            return word.substring(0, 3);
        }).findFirst();

 

결론부터 말하자면 출력 결과는 다음과 같고, 연산 결과는 "pyt"이다.

Call filter method: c
Call filter method: python
Call map method: python

 

이제 스트림의 동작 과정을 유추해보자.

메소드\데이터 c python java
filter()  
map()    
findFirst()    
  1. 배열의 첫 번째 요소인 "c"는 filter()를 거친다.
  2. "c"는 length()가 3을 넘지 않아서 map()을 거치지 않는다.
  3. 배열의 두 번째 요소인 "python"은 filter()를 거친다.
  4. "python"은 map()을 거친다.
  5. "python"은 findFirst()를 거친다.
  6. findFirst()는 조건에 맞는 하나의 결과를 찾았기 때문에 다음 요소인 "java"에 대해서는 수행하지 않는다.

 

filter()에서 모든 요소를 거친 후 다음 연산을 수행하는 방식이 아니라,

요소 하나하나에 대해 filter().map().findFirst()를 거치는 방식이다.

(표로 따지면 진행 방향이 →가 아니라 ↓)

스트림을 사용한다고 무조건적으로 성능 개선이 되는 것이 아니라 상황에 따라 알맞게, 순서를 고려하여 코딩하는 것이 바람직하다.


참조

(1) futurecreator.github.io/2018/08/26/java-8-streams/

(2) jeong-pro.tistory.com/165

(3) madplay.github.io/post/mistakes-when-using-java-streams

 

 

반응형