본문 바로가기

전체보기/Java

[Java] Stream 파헤치기(1) - Stream을 사용해야하는 이유

이번 글에서는 Java 8에서 소개된

Stream에 대해 정리해보겠습니다.

 

Stream은 다음과 같은 불편함을 해결하기 위해 만들어졌습니다.

  • Collection을 사용하는 코드를 반복적으로 구현
  • 병렬적으로 Collection을 처리하기 어려움

Collection을 사용하는 코드를 반복적으로 구현

해당 내용을 설명하기 위해 간단한 쿼리문을 가져와봤습니다.

 

SELECT id FROM transactions WHERE type = 'GROCERY' ORDER BY value DESC;

 

 

해당 쿼리문은 특정 type을 갖는 row들에 대해

높은 value를 갖는 순서대로 id를 조회하는 쿼리입니다.

 

우리는 위와 같은 쿼리문을 사용할 때,

해당 과정에 대한 깊은 이해 없이도 쉽게 사용할 수 있습니다.

 

위와 동일하게 동작하는 코드를

일반적인 loop를 이용하여 구현한다면 어떻게 구현할 수 있을까요?

 

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == TransactionType.GROCERY){
    groceryTransactions.add(t);
  }
}

Collections.sort(groceryTransactions, (t1, t2) -> t2.getValue().compareTo(t1.getValue()));

List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionIds.add(t.getId());
}

 

가독성이 떨어지는 것은 물론이고,

비슷한 로직을 구현할 때마다 위와 같은 코드가 쌓이게 됩니다.

 

Stream을 사용하면 위 코드를 간결하게 구현할 수 있습니다.

 

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

 

filter, sort, map, collect 어느 하나 제가 구현한 것은 없습니다.

모두 stream에서 제공되는 기능들입니다.

 

이로써 우리는 불필요한 기능을 구현하느라

시간을 쏟을 필요가 없어졌으며

가독성도 높은 코드를 얻게되었습니다.

병렬적으로 Collection을 처리하기 어려움

위의 stream 예제를 다시 가져와 보겠습니다.

 

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == TransactionType.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

 

코드는 간결하게 만들었으니

이번에는 코드의 성능을 높이고 싶습니다.

 

성능을 높일 수 있는 방법은 다양하게 있겠지만

Divde and Conqure 알고리즘이 방법 중 하나가 될 것 같습니다.

 

Divde and Conqure는 쉽게 이야기하면,

하나의 작업을 여러 단위로 나누고

나뉜 작업들을 병렬적으로 수행하여 작업의 효율을 높이는 방법입니다.

 

하지만 위에서 제시한 방법 역시

직접 구현하기에는 쉽지 않아보입니다.

 

Stream에서는 친절하게도 이러한 기능 역시 제공하고 있습니다.

 

List<Integer> transactionsIds = 
    transactions.parallelStream()
                .filter(t -> t.getType() == TransactionType.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

 

stream()을 parallelStream()으로 바꿔줬을 뿐이지만

내부적으로는 병렬적으로 작업을 수행하도록 구현되어있습니다.

 

'그럼 parallel stream만 쓰지 stream을 왜 써?'라는 의문이

생기시는 분도 계실 것 같습니다.

Parallel Stream을 남용할 경우 오히려 stream 보다

성능이 떨어지는 케이스가 발생할 수 있습니다.

 

위 내용에 대해서는 다음 글에서

조금 더 자세하게 다루도록 하겠습니다.

 

그럼 여기까지 Stream을 사용해야하는 이유였습니다.

Reference

 

 

 

반응형

'전체보기 > Java' 카테고리의 다른 글

[Java]Immutable class와 thread-safe  (0) 2019.08.22
[Java]ThreadLocal이란?  (6) 2019.08.06