본문 바로가기
프로그래밍/Java

[Java 8] Java 8 Stream API Tutorial

by 사바라다 2019. 11. 26.

안녕하세요. 오늘은 Java 8의 기능 중 하나인 Stream에 대해서 알아보도록 하겠습니다. Stream은 배열, List등의 요소들의 처리를 담당하는 Class로써 기존의 배열처리를 간단하게 해주며, functional style로 처리할 수 있도록 해줍니다. 그리고 병렬처리 Optional 등 다양한 처리를 할 수 있도록 지원해줍니다.

Stream 생성

Stream을 사용하기 위해서는 Stream을 생성할 필요가 있습니다. collection 또는 배열을 이용해서, 또는 자체적으로 생성할 수 있습니다. 아래와 같이 말이죠.

// from Array
String[] arr = new String[]{"a","b","c"};
Stream<String> stream = Arrays.stream(arr);

// from Collection
List<String> lists = Arrays.asList("a", "b", "c");
Stream<String> stream = lists.stream();

// only Stream Class
Strean<String> stream = Stream.of("a", "b", "c");

Stream의 멀티 스레딩

Stream API는 간단하게 multithreading으로 데이터를 처리할 수 있도록 parallelStream() 메서드를 지원해주고 있습니다. 사용방법은 상당히 편하니다만 이를 사용할 때는 반드시 내부의 로직을 알고 있어야합니다. 그 내용은 이 링크를 확인해 주시기바랍니다. [java8] 병렬 Stream 그렇지 않으면 원하지 않은 오류가 발생할 수 있습니다.

사용법은 아래와 같습니다.

list.parallelStream().forEch(element->doWork(element));

Stream Operations

일반적으로 Stream은 2가지 형태로 나눌 수 있습니다.

  • intermediate operations
    • return Stream
  • terminal operations
    • return 특정한 type의 값

이런 형태를 띄므로 intermediate operations를 통해서 chain형태로 구성할 수 있습니다. 아래 예제를 보도록 하겠습니다.

List<String> list = Arrays.asList("a", "b", "c");
long count = list.stream() // List<String> to Stream<String>
                .distinct() // return Stream<String>
                .count(); // return long

위를 설명하면 제일 먼저 String ArrayList를 Stream으로 변경합니다. 그리고 distinct() 메서드를 통해 중복되는 값을 제거합니다. 이때 distinct()에 의해서 반환되는 값은 Stream<String>입니다. 즉, 사용자는 한번더 Stream의 메서드를 사용할 수 있다는 말입니다. 그리고 마지막에 count()는 Stream에 들어있는 수를 long type으로 리턴합니다.

이런식으로 2가지의 일을 1개의 line으로 구성할 수 있다는 사실을 알게되었습니다. Stream을 이용하면 여러가지 일을 한번에 처리할 수 있게되어 간략화 시킬 수 있습니다.

Stream의 지원하는 제어

Stream이 간략화 시킬 수 있다는 사실을 알게되었습니다. 그러면 이제 Stream에서 지원하는 여러 방법에 대해서 알아보도록 하겠습니다. 모든 예제는 아래의 List를 사용한다고 하겠습니다.

List<String> list = Arrays.asList("a", "b", "c");

순환

Stream API는 for, for-each, while loop를 대신 할 수 있습니다. 아래와 같이 사용하면 말이죠.

list.stream().foreach(element -> ...somthingToDo...);

사실 stream() method에 순환이라는 개념이 들어가 있습니다. 그래서 위와같이 사용하기 보다는 순환에 대해서 특정부위를 확인하는 식으로 사용하는게 좋습니다. 아래와 같이 말이죠.

// before
for ( String string : list) {
    if(string.contains("a")) {
        return true;
    }
}

// after
boolean isExist = list.stream().anyMatch(element -> element.contains("a));

필터링

filter() 메서드는 Stream에서 특정한 내용만 통과시켜 파이프라인을 형성할 수 있도록 해줍니다.

Stream<String> stream = list.stream().filter(e -> element.contains("b"));

이렇게 반환된 stream의 요소로는 "b" 하나만을 남기고 모두 filtering되어 사라졌음을 확인할 수 있습니다. 만약 filtering 메서드를 커스터마이징 하고 싶다면 메서드를 만들 때 return 값은 boolean이라는 규칙을 지켜주시면 됩니다.

public class Compare {
    public static boolean isB(String string) {
        return string.contains("b");
    }
}

// 사용
list.stream().filter(Compare::isB); // b

이렇게 사용할 수 있게 됩니다. 혹시 lambda식에 대해서 잘 모르시겠다면 저의 이전포스터 들을 참조하시기 바랍니다.

매핑

매핑은 Stream의 요소를 특정한 규칙을 기반으로 변경할 수 있습니다.
위의 요소들을 대문자로 변경하고 싶다면 아래와같이 사용하면 됩니다.

list.stream.map(String::toUpperCase); // A, B, C

커스터마이징을 하고싶다면 메서드를 제작할 시 객체의 반환값을 가지면 됩니다.

중복제거

reduce() 메서드를 사용하면 중복으로 적어야하는 연산을 한번에 할 수 있습니다.

list.stream().reduce("1", (a, b) -> a + b); // 1abc

조합

collection() 메서드는 Stream을 다시 List, Map 등 다양한 우리가 일반적으로 알고 있는 Collection, Object로 변환해 줍니다.

List<String> resultList = list.stream().map(String::toUpperCase).collect(Collectors.toList());

resultList에는 stream연산을 통해 나온 결과가 나오게 됩니다.

마무리

제 개인적인 기준에서는 Stream이야 말로 Java 8에서 추구하는 간결성을 달성하기위한 최고의 무기가 아닐까 생각합니다. lambda식을 잘 사용하는 것만으로도 굉장하지만 이를 이용하여 Stream까지 자유롭게 사용 할 수 있다면 우리가 항상 지루하게 수십줄 씩 썼던 내용을 1줄, 2줄로 줄여버릴 수 있습니다.

감사합니다.

해당 문서는 아래 참조문서를 통해 조금 더 쉽게 이해하기 위해 재가공했습니다.

참조

https://www.baeldung.com/java-8-streams-introduction

https://www.baeldung.com/java-8-streams

댓글