Java; Stream

🗓️

스트림

스트림이란?

  • 배열 요소를 특정 기준에 따라 정렬 하거나 (sort), 요소 중 특정 값은 제외하고 출력하는 기능 (filter).
  • 이런 자료처리에 대한 기능을 구현해놓은 클래스가 stream이다.
  • 스트림을 활용하면 자료를 일관성 있게 처리할 수 있다.
  • 자료에 따라 기능을 새로 구현하는 것이 아니라 자료형에 상관없이 같은 방식으로 메소드를 호출한다.
  • 간단한 스트림 예제
int[] arr = {1,2,3,4,5};
Arrays.stream(arr).forEach(n -> System.out.println(n));
  • Arrays.stream() : 스트림의 생성 부분이다
  • forEach() : 요소를 하나씩 꺼내는 기능이다.

스트림 연산

  • 스트림의 연산 종류에는 크게 중간 연산최종 연산 두 가지가 있다.
  • 중간 연산 : 자료를 거르거나 변경하여 또 다른 자료를 내부적으로 생성한다.
  • 최종 연산 : 생성된 내부 자료를 소모하며 연산을 수행한다.

중간 연산

filter()

  • 조건을 넣고 그 조건에 맞는 참인 경우만 추출하는 경우에 사용한다.
sList.stream().filter(s -> s.length() >= 5).forEach(s -> sout(s));
  • 문자열의 길이가 5 이상인 경우만 출력하는 코드.

map()

  • 특정 요소만 가지고 와서 출력하는 경우에 사용한다.
  • map()은 요소를 순회해 다른 형식으로 변환하기도 한다.
customerList.stream().map(c -> c.getName()).forEach(s -> sout(s));
  • 클래스에서 getName()으로 클래스의 특정 요소만 가지고오는 코드.

최종 연산

  • forEach() : 요소 순회
  • count() : 배열 요소의 개수
  • sum() : 배열 요소의 합계
  • reduce() : (아래에서 다시 설명)

스트림 생성하고 사용하기

정수 배열에 스트림 생성하고 사용하기

  • 코드보기

Collection에서 스트림 생성하고 사용하기

  • ArrayList에 스트림을 생성하고 활용해보자.
  • Stream<E> stream() : 스트림 클래스를 반환한다.
  • Collection에서 stream()를 이용하면 클래스는 제네릭형을 사용해 아래와 같이 자료형 명시가 가능하다
Stream<Stream> strm = sList.stream();
  • 이렇게 생성된 스트림은 내부적으로 ArrayList의 모든 요소를 가지고 있다.

forEach()

  • forEach()를 활용해 모든 요소를 가지고 와본다
strm.forEach(s -> sout(s));
  • forEach()내의 람다식의 s에 하나씩 변수가 들어가고 출력문이 실행된다.

sorted()

  • sorted()를 활용해 모든 요소를 정렬해본다.
strm.sorted().forEach(s -> sout(s));
  • 중간 연산으로 sorted()를 활용하면 요소를 정렬할 수 있다.
  • 기본 정렬 이외에 지정된 정렬방식이 있다면, Comparable를 구현하거나 Comparator 클래스를 sorted()의 매게변수로 지정해줘야 한다.
  • 코드보기

스트림의 특징

자료의 대상과 관계 없이 동일한 연산을 수행한다

  • 배열이나 컬렉션에 저장된 자료를 가지고 수행할 수 있는 연산은 여러가지가 있다.
  • 단순 출력, 조건에 따른 필터링, 합계 또는 평균.
  • 스트림은 컬렉션의 여러 자료구조에 대해 이러한 작업을 일관성 있게 처리할 수 있다.

한 번 생성하고 사용한 스트림은 재사용할 수 없다

  • 스트림을 생성하고 메소드를 호출해 연산을 수행했다면 다시 사용할 수 없다.
  • 만약 다른 기능을 호출 하려면 스트림을 새로 생성해야한다.

스트림의 연산은 기존 자료를 변경하지 않는다.

  • 스트림을 생성하면서 중간연산으로 정렬이나 합을 구한다고 하더라도 원본 자료는 변하지 않는다.
  • 스트림 연산을 위한 메모리 공간이 별도로 존재한다.

스트림의 연산은 중간 연산과 최종 연산이 있다.

  • 중간연산은 여러개가 적용 될 수 있다.
  • 최종연산은 마지막 하나만 적용된다.
  • 중간연산이 여러개 호출되더라도 최종연산이 있어야 중간 연산들이 모두 적용된다. – 이것을 지연 연산(lazy evaluation)이라고 한다.

기능을 지정하는 reduce() 연산

  • reduce()는 내부적으로 스트림의 요소를 하나씩 소모하면서 프로그래머가 직접 지정한 기능을 수행한다.
  • reduce()원형
T reduce(T idnetify, BinaryOperator<T> accumulator)
  • T idnetify : 초기값
  • BinaryOperator<T> accumulator : 수행해야 할 기능
  • BinaryOperator 인터페이스는 두 매개변수로 람다식을 구현한다. 람다식이 각 요소가 수행해야할 기능이 된다.
  • 람다식을 직접 넣어도 되고 인터페이스를 구현한 클래스를 생성하여 대입해도 된다.
  • BinaryOperator는 Functional Interface로 apply()를 반드시 구현해야한다.
  • apply()두 개의 매개변수와 한개의 반환값을 가진다. 세 개 모두 같은 자료형이다.
  • reduce()가 호출될 때 BinaryOperatorapply()가 호출된다.
  • 요소의 합에 대해 reduce()를 사용한 예
Arrays.stream(arr).reduce(0, (a , b) -> a + b));
  • 0 : 초기값
  • (a, b) : 전달되는 요소
  • -> a + b : 각 요소가 수행해야 하는 기능
  • 가장 긴 문자열을 찾아내는 reduce()예제
class CompareString implements BinaryOperator<String> {

    @Override
    public String apply(String s, String s2) {
        if (s.getBytes().length >= s2.getBytes().length) {
            return s;
        } else {
            return s2;
        }
    }
}

public class ReduceTest {

    public static void main(String[] args) {
        String[] greetings = {"안녕하세요", "헬로", "곤방와", "봉쥬르"};
        System.out.println(
            Arrays.stream(greetings)
                .reduce("", (s1, s2) -> {
                    if (s1.getBytes().length >= s2.getBytes().length) {
                        return s1;
                    } else {
                        return s2;
                    }
                })
        );

        String str = Arrays.stream(greetings).reduce(new CompareString()).get();
        System.out.println(str);
    }
}
  • 첫번째 스트림에서는 람다식 내 직접 구현하고 있고
  • 두번째 스트림은 구현한 인터페이스를 생성하여 사용하고 있다. apply()가 자동으로 호출된다.

스트림을 활용하여 여행객의 여행 비용 계산하기

public class TravelTest {

    public static void main(String[] args) {
        List<TravelCustomer> customerList = new ArrayList<>();
        customerList.add(new TravelCustomer("MINKANG", 30, 100));
        customerList.add(new TravelCustomer("JIN-LEE", 31, 200));
        customerList.add(new TravelCustomer("JONGPAK", 16, 300));

        System.out.println("== in-order customer list ==");
        customerList.stream()
            .map(c -> c.getName())
            .forEach(s -> System.out.println(s));

        int total =
            customerList.stream()
                .mapToInt(c -> c.getPrice())
                .sum();
        System.out.println("Total travel cost: " + total);

        System.out.println("== Customer list that age over 20");
        customerList.stream()
            .filter(c -> c.getAge() >= 20)
            .map(c -> c.getName())
            .sorted()
            .forEach(s -> System.out.println(s));

    }

}
  • 실행결과
== in-order customer list ==
MINKANG
JIN-LEE
JONGPAK
Total travel cost:600
== Customer list that age over 20
JIN-LEE
MINKANG