반응형

스트림(Stream)

스트림(Stream)은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

 

스트림은 자바8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다. Iterator와 비슷한 역할을 하지만 람다식으로 요소 처리 코드를 제공하여 코드가 좀 더 간결하게 할 수 있다는 점과 내부 반복자를 사용하므로 병렬처리가 쉽다는 점에서 차이점이 있습니다. 

 

스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.

  1. 생성하기 : 스트림 인스턴스 생성.
  2. 가공하기 : 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations).
  3. 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).

1. 생성하기

배열 스트림

 

String[] arr = new String[]{"Bumblebee", "b", "c"};
Stream<String> stream = Arrays.stream(arr); // return Bumblebee, b, c
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3); // return b, c

 

컬렉션 스트림

 

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> colStream = list.stream(); // return a, b, c
Stream<String> parallelStream = list.parallelStream();  // 병렬 처리 스트림

 

Stream.builder()

 

Stream<String> builderStream =
Stream.<String>builder()
                        .add("kakao").add("naver").add("google")
                        .build(); // return kakao, naver, google

 

Stream.generate()

생성되는 스트림은 크기가 정해져있지 않고 무한(infinite)하기 때문에 특정 사이즈로 최대 크기를 제한해야 합니다.

 

 

Stream<String> generatedStream =
Stream.generate(() -> "Bumblebee").limit(3); // return Bumblebee, Bumblebee, Bumblebee

 

Stream.iterate()

초기값 설정 후 해당 값을 람다를 통해서 스트림에 들어갈 요소를 만들 수 있다.

 

Stream<Integer> iteratedStream = Stream.iterate(10, n -> n + 2).limit(3); // return 10, 12, 14

 

기본 타입형 스트림

리스트나 배열을 이용해서 기본 타입(int, long, double) 스트림을 생성할 수 있습니다.

 

IntStream intStream = IntStream.range(1, 5); // return 1,2,3,4
LongStream longStream = LongStream.rangeClosed(1, 5); // return 1,2,3,4,5

 

Java 8 의 Random 클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream, LongStream, DoubleStream)을 만들어낼 수 있습니다.

DoubleStream doubles = new Random().doubles(3);

 

병렬 스트림 Parallel Stream

스트림 생성 시 사용하는 stream 대신 parallelStream 메소드를 사용해서 병렬 스트림을 쉽게 생성할 수 있습니다.

내부적으로는 쓰레드를 처리하기 위해 자바 7부터 도입된 Fork/Join framework 를 사용합니다.

각 작업을 쓰레드를 이용해 병렬 처리됩니다.

 

Stream<Product> parallelStream = productList.parallelStream(); // 병렬 스트림 생성
boolean isParallel = parallelStream.isParallel(); // 병렬 여부 확인

 

다시 시퀀셜(sequential) 모드로 돌리고 싶다면 다음처럼 sequential 메소드를 사용합니다.

 

parallelStream.sequential(); // sequential
boolean isParallel = parallelStream .isParallel();

 

스트림 연결하기

 

Stream<String> stream1 = Stream.of("kakao", "naver");
Stream<String> stream2 = Stream.of("google");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(a -> System.out.print(a+", ")); // kakao, naver, google

 

2. 가공하기

전체 요소 중에서 다음과 같은 API 를 이용해서 내가 원하는 것만 뽑아낼 수 있습니다.

이러한 가공 단계를 중간 작업(intermediate operations)이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙여서(chaining) 작성할 수 있습니다.

 

Filtering

필터(filter)은 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업입니다. 

 

List<String> itCompany = Arrays.asList("kakao", "naver", "google"); // 테스트 dataset

Stream<String> filterStream = itCompany.stream().filter(company -> company.contains("a")); // return kakao, naver

 

Mapping

맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환해줍니다. 이 때 값을 변환하기 위한 람다를 인자로 받습니다.

 

Stream<String> mapStream = itCompany.stream().map(String::toUpperCase); // return KAKAO, NAVER, GOOGLE

 

Sorting

 

List<String> sortStream = itCompany.stream()
                                            .sorted()
                                            .collect(Collectors.toList()); //return google, kakao, naver,


List<String> sortReverseStream = itCompany.stream()
                                                  .sorted(Comparator.reverseOrder()) //역순
                                                  .collect(Collectors.toList()); // return naver, kakao, google

 

Comparator 의 compare 메소드는 두 인자를 비교해서 값을 리턴합니다.

 

List<String> compareSortStream = itCompany.stream()
                                                   .sorted(Comparator.comparingInt(String::length))
                                                   .collect(Collectors.toList()); // return kakao, naver, google


List<String> collect = itCompany.stream()
                                        .sorted((s1, s2) -> s2.length() - s1.length())
                                        .collect(Collectors.toList()); // reutrn google, kakao, naver,

 

peek

확인해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 인터페이스 Consumer 를 인자로 받습니다.

 

IntStream.of(1, 3, 5, 7, 9)
                            .peek(System.out::println)
                            .sum(); //return 1, 3, 5, 7, 9

 

 

3. 결과 만들기

가공한 스트림을 가지고 내가 사용할 결과값으로 만들어내는 단계입니다. 따라서 스트림을 끝내는 최종 작업(terminal operations)입니다.

 

Calculating

스트림 API 는 다양한 종료 작업을 제공합니다. 최소, 최대, 합, 평균 등 기본형 타입으로 결과를 만들어낼 수 있습니다.

 

long count = IntStream.of(1, 2, 3, 4, 5).count(); //return 5
long sum = LongStream.of(1, 2, 3, 4, 5).sum();  //return 15

 

만약 스트림이 비어 있는 경우 count  sum 은 0을 출력하면 됩니다. 하지만 평균, 최소, 최대의 경우에는 표현할 수가 없기 때문에  Optional을 이용해 리턴합니다.

 

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min(); //return OptionalInt[1]
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max(); //return OptionalInt[9]

 

Reduction

스트림은 reduce라는 메소드를 이용해서 결과를 만들어냅니다.

 

reduce 메소드는 총 세 가지의 파라미터를 받을 수 있습니다.

  1. accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
  2. identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
  3. combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.

1. accumulator 

 

OptionalInt reduced =
IntStream.range(1, 4) //[1, 2, 3]
.reduce((a, b) -> {
    return Integer.sum(a, b);
});  // return OptionalInt[6]

 

2. identity

10은 초기값이고, 스트림 내 값을 더해서 결과는 16이 됩니다.

여기서 람다는 메소드 참조(method reference)를 이용해서 넘길 수 있습니다.

 

int reducedTwoParams =
IntStream.range(1, 4) // [1, 2, 3]
.reduce(10, Integer::sum); // return 16

 

3.combiner

Combiner 는 병렬 처리 시 각자 다른 쓰레드에서 실행한 결과를 마지막에 합치는 단계입니다.

따라서 병렬 스트림에서만 동작합니다.

 

Integer reducedParams = Stream.of(1, 2, 3)
.reduce(10, // identity
Integer::sum, // accumulator
(a, b) -> {
   System.out.println("combiner was called"); // 병렬 스트림이 아니어서 호출 안됨
   return a + b;
}); // return 16

 

결과는 다음과 같이 36이 나옵니다. 먼저 accumulator 는 총 세 번 동작합니다. 초기값 10에 각 스트림 값을 더한 세 개의 값(10 + 1 = 11, 10 + 2 = 12, 10 + 3 = 13)을 계산합니다. Combiner 는 identity 와 accumulator 를 가지고 여러 쓰레드에서 나눠 계산한 결과를 합치는 역할입니다. 12 + 13 = 25, 25 + 11 = 36 이렇게 두 번 호출됩니다.

 

Integer reducedParallel = Arrays.asList(1, 2, 3)
.parallelStream() // 병렬 스트림
.reduce(10,
Integer::sum,
(a, b) -> {
   System.out.println("combiner was called"); // 두번 호출 됨
   return a + b;
}); // return 36

 

Collecting

- Product.java

public class Product {
int count;
String name;

public Product(int count, String name) {
this.count = count;
this.name = name;
}

public String getName() {
return this.name;
}

public int getAmount() {
return this.count;
}
}

// 테스트 dataset

List<Product> productList = Arrays.asList(new Product(23, "potatoes"),
                                                      new Product(14, "orange"),
                                                      new Product(13, "lemon"),
                                                      new Product(23, "bread"),
                                                      new Product(13, "sugar"));

 

Collectors.toList()

 

List<String> collectorCollection = productList.stream()
                                                      .map(Product::getName)
                                                   	.collect(Collectors.toList());

// return potatoes, orange, lemon, bread, sugar

 

Collectors.joining()

 

String listToString = productList.stream()
                                         .map(Product::getName)
                                         .collect(Collectors.joining()); // reutrn potatoesorangelemonbreadsugar

 

 

  • delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
  • prefix : 결과 맨 앞에 붙는 문자
  • suffix : 결과 맨 뒤에 붙는 문자
String listToString2 = productList.stream()
                                         .map(Product::getName)
                                         .collect(Collectors.joining(", ", "<", ">"));

// reutrn <potatoes, orange, lemon, bread, sugar>

 

Collectors.averageingInt()

 

Double averageAmount = productList.stream()
                                         .collect(Collectors.averagingInt(Product::getAmount)); // return 17.2

 

Collectors.summingInt()

 

Integer summingAmount = productList.stream()
                                          .collect(Collectors.summingInt(Product::getAmount)); // return 86

 

IntStream 으로 바꿔주는 mapToInt 메소드를 사용해서 좀 더 간단하게 표현할 수 있습니다.

 

Integer summingAmount = productList.stream()
                                           .mapToInt(Product::getAmount)
                                           .sum(); // return 86

 

Collectors.summarizingInt()

 

IntSummaryStatistics statistics =productList.stream()
                                                   .collect(Collectors.summarizingInt(Product::getAmount));

//return IntSummaryStatistics{count=5, sum=86, min=13, average=17.200000, max=23}

 

 

참고

- Java 스트림 Stream (1) 총정리(https://futurecreator.github.io/2018/08/26/java-8-streams/)

- [Java] 자바 스트림(Stream) 사용법 & 예제(https://coding-factory.tistory.com/574)

728x90
반응형

'JAVA' 카테고리의 다른 글

JAVA int, Integer 비교  (0) 2023.03.16

+ Recent posts