Back-end/Java Language

스트림(stream)

prden 2021. 10. 13. 16:52

1. 스트림이 생기게 된 배경

통상적으로 많은 수의 데이터를 다룰 때 컬렉션이나 배열에 데이터를 담고 for문과 Iterator를 이용해서 코드를 작성해왔다. 그러나 이러한 방식으로 코드를 작성한다면 너무 길고 알아보기 어려우며 재사용성도 떨어진다. 또한, 데이터 소스마다 다른 방식으로 다루어야 한다.

예를 들어 List를 정렬할 때는 Collections.sort(); 배열을 정렬할 때는 Arrays.sort();

이러한 문제점을 해결하기 위해 스트림이 만들어졌다. 스트림은 데이터 소스를 추상화 함으로써 데이터 소스가 무엇이든 간에(List, Set, Map, 배열, 파일에 저장된 데이터 등) 같은 방식으로 다룰 수 있게 되었으며 이로 인해 코드의 재사용성이 높아졌다. 

 

2. 스트림 생성

 //주문 상품 생성
 
       List<OrderDetails> orderDetailsList = orderRequest.getOrderDetailsList()
              .stream() //스트림을 생성 .stream()
              .map(od -> {
                  Product product = productRepository.findById(od.getProductId())
                          .get();
                  return new OrderDetails(product, od.getOrderQuantity());
               }).collect(Collectors.toList());

 

3. 스트림의 특징

 1) 스트림은 데이터 소스를 변경하지 않는다. 

  스트림은 데이터 소스로부터 데이터를 읽기만할 뿐, 데이터 소스를 변경하지 않는다. 필요하다면 정렬된 결과를 컬렉션이나 배열에 담아서 반환할 수 있다. 

// 정렬된 결과를 새로운 List에 담아서 반환

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

2) 스트림은 일회용이다. 

 스트림을 한 번 사용한다면 닫혀서 다시 사용할 수 없다. 필요하다면 스트림을 다시 생성해야 한다. 

 

3) 스트림은 작업을 내부 반복으로 처리한다. 

내부 반복이란 반복문을 메서드의 내부에 숨길 수 있다는 것을 의미한다.

예를 들어

for(String str : strList){
	System.out.println(str)
    };
    
// 위랑 같은 것
stream.forEach(System.out::println);

4. map()

스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때가 있다. 이때 사용하는 것이 바로 map()이다. 

List<String> threeHighCaloricDishNames =
	menu.stream() //요리 리스트에서 스트림을 얻는다. 
    	.filter(dish -> dish.getCalories()>30) // 파이프라인 연산 만들기 고칼로리 요리를 필터링
        .map(Dish::getName) // 요리명 추출
        .limit(3) // 선착순 세 개만 선택
        .collect(toList()); // 결과를 다른 리스트로 저장
   System.out.println(threeHighCaloricDishName); // 결과는 3개 고칼로리 요리 나옴

map()은 연산의 결과로 Stream<T>타입의 스트림을 반환하는데, 스트림의 요소를 숫자로 변환하는 경우 IntStream과 같은 기본형 스트림으로 변환하는 것이 더 유용할 수 있다. 스트림을 기본형 스트림으로 변환할 때 사용하는 것이 아래의 메서드 들이다. 

1) DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

2) mapToInt mapToInt(ToIntFunction<? super T> mapper)

3) mapToLong mapToLong(ToLongFunction<? super T>mapper)

//성적을 더할 때 다음과 같이 하는 것이 이상적이다. 
IntStream StudentScoreStream = studentStream.mapToInt(Student::getTotalScore);
int allTotlaScore = studentScoreStream.sum(); // int sum();

//성적을 더할 때 Integer를 int로 변환할 필요가 없기 때문이다.

5. forEach()

최종 연산은 스트림의 요소를 소모해서 결과를 만들어낸다. 그래서 최종연산 후에는 스트림이 닫히게 되고 더 이상 사용할 수 없다. 

최종 연산의 결과는 스트림 요소의 합과 같은 단일 값이거나, 스트림의 요소가 담긴 배열 또는 컬렉션일 수 있다. 

 

1) forEach() : 반환 타입이 void이므로 스트림의 요소를 출력하는 용도로 많이 사용된다.  (peek는 forEach와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않는다. )

 //주문 상품 재고 줄이기
        orderDetailsList.stream()
               .forEach(orderQuantity -> orderQuantity.reduceStockQuantity());

 

'Back-end > Java Language' 카테고리의 다른 글

instanceof, java  (0) 2022.03.17
System.out과 logging  (0) 2021.12.05
변수명 짓는 방법  (0) 2021.09.17
@SuppressWarnings  (0) 2021.09.08
ENUM  (0) 2021.07.30