관리 메뉴

나만을 위한 블로그

[JAVA] Stream API란? 본문

JAVA

[JAVA] Stream API란?

참깨빵위에참깨빵 2021. 7. 20. 00:12
728x90
반응형

Stream API는 자바 8에서 추가된 기능인데 컬렉션, 배열 등에 저장된 요소들을 하나씩 참조해서 람다식을 통해 반복적으로 어떤 처리를 할 수 있도록 해주는 기능이다.

자세한 기능 및 내용은 아래를 참고했다.

https://www.geeksforgeeks.org/stream-in-java/

 

Stream In Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

스트림은 데이터 구조가 아니라 컬렉션, 배열 또는 I/O에서 입력을 받는다.
스트림은 원래 데이터 구조를 변경하지 않으며 파이프라인 방식에 따라 결과만 제공한다.
각 중간 작업은 느리게 실행되고 결과적으로 스트림을 반환하므로 다양한 중간 작업이 파이프라인될 수 있다. 터미널 작업은 스트림의 끝을 표시하고 결과를 반환한다.

https://www.tutorialspoint.com/java8/java8_streams.htm

 

Java 8 - Streams - Tutorialspoint

Java 8 - Streams Stream is a new abstract layer introduced in Java 8. Using stream, you can process data in a declarative way similar to SQL statements. For example, consider the following SQL statement. SELECT max(salary), employee_id, employee_name FROM

www.tutorialspoint.com

스트림은 자바 8에 도입된 새로운 추상 계층이다. 스트림을 사용하면 SQL 문과 유사한 선언적 방식으로 데이터를 처리할 수 있다. 개발자는 자바의 컬렉션 프레임워크를 사용해서 루프를 사용하고 반복적으로 확인해야 한다.
또 다른 문제는 효율성이다. 멀티코어 프로세서를 쉽게 사용할 수 있으므로 자바 개발자는 오류가 발생하기 쉬운 병렬 코드 처리를 작성해야 한다. 이런 문제를 해결하기 위해 자바 8은 개발자가 특정 코드를 작성할 필요 없이 데이터를 선언적으로 처리하고 멀티코어 아키텍처를 활용할 수 있도록 하는 스트림 개념을 도입했다. 스트림은 집계 작업을 지원하는 소스의 개체 시퀀스를 나타낸다.

https://velog.io/@litien/Stream-API

 

Stream API

Stream API 란 Java 8에서 새롭게 추가된 Api로 함수형 인터페이스(람다식)을 적용하여 컬렉션과 같은 저장요소를 반복적으로 처리할 수 있는 기능이다. java에서 완전한 Funtional Programming은 아닐지라도

velog.io

스트림은 Immutable하다. 다시 말해 원본 데이터를 변경하지 않는다.
스트림의 연산은 lazy하다. 즉 필요할 때만 연산해서 효율적인 처리가 가능하다.
스트림은 재사용이 불가능하다.
Stream API를 써서 얻는 이점은 코드의 가독성이 높아지고 실수의 여지를 줄여준다는 것이다.

 

그럼 이제 코드를 보면서 for문과 Stream API의 차이에 대해 확인해보자.

먼저 이런 요구사항이 있다고 가정해보자.

사람 리스트가 주어지면 이름이 null이 아니고, 이름이 "B"로 시작하는 19~65세 사이의
모든 남자 형제의 이름을 가져와라

 

보기만 해도 아찔하다. 조건을 몇 번이나 걸어야 할지...

일단 스트림을 쓰지 않는다면 for와 if를 사용해서 고민한다고 고민해서 코드를 짤 것이다.

먼저 Person 클래스를 정의해준다. 이름과 나이, 형제, 성별이라는 속성이 있으니 이걸 그대로 코드로 바꿔치기해서 적당히 만들겠다.

import java.util.List;

public class Person
{
    private String name;
    private int age;
    private String gender;
    private List<Person> siblings;

    public Person(String name, int age, String gender, List<Person> siblings)
    {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.siblings = siblings;
    }

    public String getName()
    {
        return name;
    }

    public int getAge()
    {
        return age;
    }

    public String getGender()
    {
        return gender;
    }

    public List<Person> getSiblings()
    {
        return siblings;
    }
}

 

"모든 남자 형제" 라는 복수 형태의 단어를 썼기 때문에 siblings는 List로 선언한 걸 볼 수 있다. 여기까진 뭐 오케이.

이제 요구사항에 맞게 로직을 짜본다.

public static void getInformationOfPersonForLoop()
    {
        List<Person> persons = List.of();
        List<String> result = new ArrayList<>();

        for (Person p : persons)
        {
            for (Person sibling : p.getSiblings())
            {
                if (sibling.getGender().equals("M") && sibling.getAge() > 18 && sibling.getAge() <= 65
                        && sibling.getName() != null && sibling.getName().startsWith("B"))
                {
                    result.add(sibling.getName());
                }
            }
        }
    }

 

혹은 아래와 비슷한 형태의 코드가 나올 것이다.

public static void getInformationOfPerson()
    {
        List<Person> persons = List.of();
        List<String> result = new ArrayList<>();

        for (Person p : persons)
        {
            for (Person sibling : p.getSiblings())
            {
                if (sibling.getGender().equals("M"))
                {
                    if (sibling.getAge() > 18 && sibling.getAge() <= 65)
                    {
                        if (sibling.getName() != null && sibling.getName().startsWith("B"))
                        {
                            result.add(sibling.getName());
                        }
                    }
                }
            }
        }
    }

 

짤 때는 그렇다 치더라도 당장 몇 달 뒤에 이 코드를 보면 조금 시간이 걸릴 수 있다. 무슨 기능을 왜 만들어야 했었는지부터 시작해서 로직도 확인해야 하니까.

이걸 Stream API를 적용해 바꾼다면 아래와 같이 바뀐다. 둘 다 같은 작업을 수행하는 코드다.

 

public static void getInformationOfPersonStream()
    {
        List<Person> persons = List.of();
        List<String> result = persons.stream()
                .flatMap(p -> p.getSiblings().stream())
                .filter(p -> p.getGender().equals("M"))
                .filter(p -> p.getAge() > 18)
                .filter(p -> p.getAge() <= 65)
                .filter(p -> p.getName() != null)
                .filter(p -> p.getName().startsWith("B"))
                .map(p -> p.getName())
                .collect(Collectors.toList());
    }

 

오..뭔가 조금 더 간결해진 느낌이고 코드가 직관적이다.

flatMap은 뭔지 잘 모르겠지만 filter는 필터, 즉 거른다는 뜻의 단어니까 () 안에 있는 내용들로 필터해서 데이터를 거른 다음 getName()으로 걸러진 데이터들에서 이름만 뺀 다음, 그것을 리스트로 변환해서 돌려준다는 뜻의 코드라는 걸 알 수 있다.

for와 if를 써도 충분히 제 기능을 하는 코드를 짤 순 있다. 하지만 요구사항이 이것보다 더 복잡해졌을 경우 코드는 분명 어려워지고 그걸 다음에 보게 될 사람은 코드 분석에 상당히 애를 먹게 될 텐데, Stream API를 사용하면 이것에 대한 지식이 있을 경우 좀 더 수월하게 코드를 파악해 작업할 수 있어 유지보수에 도움이 될 것 같다고 생각된다.

 

그러나 스트림은 쓰레드처럼 재사용이 불가능하다는 특징이 있고, 병렬 스트림을 만들어 작업할 경우 여러 쓰레드가 달라붙어 처리하기 때문에 앱이나 프로그램에서 사용하는 쓰레드가 많거나 스트림 안의 요소 수가 많지 않다면 오히려 그것에 수반되는 오버헤드가 더 클 수 있다. 배보다 배꼽이 더 큰 격이다.

그러니 스트림을 써야 하는 상황인지, 단순 for와 if를 써서 충분히 구현할 수 있고 그렇게 해도 문제가 없는지를 잘 따져서 사용해야 할 것 같다.

반응형
Comments