본문 바로가기
개발/자바

기본형 특화 스트림(IntStream, LongStream, DoubleStream)

by hamcheeseburger 2022. 10. 9.

Stream을 사용하다보면 mapToInt(), mapToLong() 과 같은 메서드를 본 적이 있을 것이다.

필자는 map() 과 비교했을 때 결과적으로 다른 것은 없지만, 각 타입에 대한 연산 처리 과정에서 효율적일 것이라는 뇌피셜을 가지고 사용하고 있었다.

정말 그럴까? 🤔 예제를 통해서 살펴보자.

 

기본형 특화 스트림이 뭘까?

Stream에서 mapToInt(), mapToLong(), mapToDouble() 을 호출하면 각각 IntStream, LongStream , DoubleStream 이 반환된다.

해당 Stream들을 모두 기본형 특화 스트림이라고 부른다. 말 그대로 기본형, primitive type에 특화된 Stream 이라는 의미이다.

앞서서 뇌피셜로 각 타입에 대한 연산 처리 과정에서 효율적일 것이라고 했는데, 사실 이는 틀린말이다.

기본형 특화 스트림은 박싱 비용을 줄이기 위해 특화된 Stream (언박싱은 x)이라고 이해하는 것이 맞다.

어떻게 박싱 비용을 줄이는지 아래의 예제를 통해 살펴보자.

기본형 특화 스트림이 없다면.

기본형 특화 스트림이 존재하지 않다고 가정하고, 각 타입에 대한 변환과 총 합계와 같은 연산을 구현해보자.

가볍게 생각해보면 각 타입에 대한 변환은 map() 으로, 총 합계와 같은 연산은 reduce() 로 진행해야 겠다는 생각이 들 것이다. 코드로 살펴보자.

public class Dish {
    private int price;
    // getter
}

Dish라는 클래스 안에 primitive type의 price 변수가 존재한다.

dishes.stream()
    .map(Dish::getPrice())
    .reduce(0, Integer::sum());

dishes는 Dish가 여러개 담긴 List이다.

Integer.sum()의 파라미터 타입이 privimitive type인 int임을 알아둔채로 과정을 다시 살펴보자.

파라미터로 primitive type인 int를 전달받고 있다.

  1. dishes.stream() : dishes를 순회를 돈다.
  2. .map(Dish::getPrice()) : 각 요소의 price를 불러오고 Integer 형태로 박싱한다.
  3. .reduce(0, Integer::sum()) :변환된 요소들을 순차적으로 Integer.sum()에 전달한다.
  4. Integer::sum() : Integer 타입의 요소들이 Integer.sum()에 전달되면서 primitive type으로 언박싱된다.

어차피 최종적으로 primitive type으로 연산이 이루어질 요소들인데 불필요하게 박싱 언박싱 비용이 발생한다.

 

기본형 특화 스트림을 사용한다면.

map() 을 수행 하면 반환값으로 Stream<T>가 반환되어야 하기 때문에, 무조건 Wrapper class로 박싱이 될 수 밖에 없다.

이렇게 불필요한 박싱 연산을 줄이기 위해 나온 것이 기본형 특화 스트림이다.

코드를 기본형 특화 스트림으로 바꿔보자.

dishes.stream()
    .mapToInt(Dish::getPrice())
    .reduce(0, Integer::sum());
  1. dishes.stream() : dishes를 순회를 돈다.
  2. .mapToInt(Dish::getPrice()) : 각 요소의 price를 primitive 타입으로 불러온다.
  3. .reduce(0, Integer::sum()) : 요소들을 순차적으로 Integer.sum()에 전달한다.

mapToInt()

mapToInt()를 진행할 때의 과정을 좀 더 살펴보자.

mapToInt는 ToIntFunction 이라는 funtional Interface를 파라미터로 받고 있다.

applyAsInt()를 살펴보면 무조건 int 를 반환하게 되어있다.

즉, 전달 받는 값이 무엇이든 int 로 반환이 되어야 한다. value로 int 형 타입이 전달되면 그대로 int형 타입이 반환될 것이고, Integer 형 타입이 전달되면 언박싱되어 int 형 타입으로 반환될 것이다.

앞선 예제에서 Dish가 갖는 price의 타입은 int 타입이었으니 어떠한 박싱 언박싱 작업이 이루어지지 않고 그대로 값이 반환이 된다.

sum()

위의 예제에서는 반환된 결과의 총합을 구할 때 reduce() 연산을 사용하였다. 그런데 IntStream에서 기본적으로 제공하는 sum() 이라는 메소드가 있다. 해당 메서드를 사용하면 조금 더 가독성을 높이면서 관심사에 맞게 사용할 수 있다.

dishes.stream()
    .mapToInt(Dish::getPrice())
    .sum();

그렇다면 기능적으로 어떠한 차이가 있을까? IntStream에서 구현한 것이니 뭔가 더 효율적인 연산이 들어가지는 않을까?

IntStream의 구현체인 IntPipeline 의 sum() 메서드다. 결론적으로 reduce() 를 사용하고 있으므로 다른게 전혀 없다.

그래서 서론에서 얘기한 것 처럼 연산의 처리과정에서는 기본형 특화 스트림이 도움을 주지는 못한다.

 

sum()뿐만 아니라 max(), min()라는 메서드를 제공하는데, 이것들도 reduce()를 사용하고 있다.

 

기본형 특화 스트림이 의미없는 상황.

기본형 특화 스트림이 의미 없는 상황에 대해서 살펴보자.

public class Dish {
    private Integer price;
    // getter
}
dishes.stream()
    .mapToInt(Dish::getPrice())
    .count();

위의 코드처럼, 만약에 Dish가 갖는 price의 타입이 Integer 이라면 mapToInt() 과정에서 int 타입으로의 언박싱이 이루어질 것이다.

이후의 count()과 같은 연산에서 primitive type이 사용될 일이 없다면 위의 상황에서는 기본형 특화 스트림을 사용하는 것이 오히려 의미 없을 수도 있다.

요약

기본형 특화 스트림은 불필요한 박싱이 수행되지 않도록 해주는 스트림이다.

Stream 연산에서 인자가 박싱될 필요 없이 primitvie type으로 유지되어야 한다면 기본형 특화 스트림을 이용하면 된다.

'개발 > 자바' 카테고리의 다른 글

Java Atomic  (1) 2022.12.03
Java Closure  (0) 2022.12.03
함수형 인터페이스 (Functional Interface)  (6) 2022.03.30
추상화란?  (2) 2022.03.17
compareTo()가 0을 반환하면 어떤 순서로 정렬되나?  (0) 2022.02.19

이전 댓글