[Java] Stream API

RedPanda·2024년 11월 6일
0

[Java] Spring Boot

목록 보기
6/7

Spring boot로 백엔드 코드를 작성할 때, 처음 배운 방식대로 Java 코드를 작성해왔다. for문으로 반복문을 돌리면서 필요시에 if문으로 필터링하는 코드를 많이 작성해왔다. 문제는 이렇게 작성하면 코드 길이가 쓸데없이 길어지고 가독성이 떨어진다는 것이다.

개발자들은 항상 간결하고 작성하기 쉬운 코드를 원한다. 내가 직면한 이 문제는 이들이 만든 Stream API로 해결할 수 있겠다.

Stream API란

자바 8에서 추가한 스트림은 람다를 활용하여 배열 및 컬렉션을 다룰 수 있는 기술 중 하나이다. 이전에 사용했던 방법인 for 또는 foreach 문과는 달리 람다와 여러 매소드를 사용하여 간결한 코드 작성과 원하는 필터링에 대한 결과를 도출할 수 있다.
또한 간단하게 병렬처리를 사용하여 멀티스레드로 많은 요소들을 빠르게 처리할 수 있는 장점이 있다.

사용 방법

stream의 흐름은 이러하다.

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

생성하기

사용할 만한 메소드를 알아보자.

  • stream() : stream을 생성한다.
Arrays.stream(arr)	// Arrays를 Stream으로 변환
Collection.stream()	// Collection을 Stream으로 변환 (ex. ArrayList)
  • streamof() : 빈 스트림도 생성할 수 있다.

  • generate(func) : 람다를 사용하여 특정 값으로 스트림을 생성한다. limit(int)으로 크기 제한 필요

Stream<String> generatedStream = 
  Stream.generate(() -> "gen").limit(5); // ["gen","gen","gen","gen","gen"]
  • iterate(init, lambda) : 기입된 초기값에 lambda를 사용하여 스트림을 생성한다.
Stream<Integer> iteratedStream = 
  Stream.iterate(30, n -> n + 2).limit(5); // [30, 32, 34, 36, 38]
  • Stream.concat(stream1, stream2) : 스트림을 합친다.
Stream<String> stream1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream2 = Stream.of("Python", "Go", "Swift");
Stream<String> concat = Stream.concat(stream1, stream2);
// [Java, Scala, Groovy, Python, Go, Swift]

가공하기

method chaining을 사용해서 필터링을 하는 중간 작업이다.

  • Stream.filter(lambda) : stream 요소를 람다식으로 평가한다.
  • Stream.map(lambda) : stream 요소에 람다식을 하나씩 사용한다.
Stream<Integer> stream = 
  productList.stream()
  .map(Product::getAmount);
// [23, 14, 13, 23, 13]

List<String> flatList = 
  list.stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList());
// [[a],[b]] => [a, b]
  • Stream.sorted(Comparator) : Comparator로 스트림을 정렬한다. 인자를 사용하면 원하는 정렬이 가능하다.
ex) Comparator.comparingInt(Int) , Comparator.reverseOrder()

IntStream.of(14, 11, 20, 39, 23)
  .sorted()
  .boxed()
  .collect(Collectors.toList());
// [11, 14, 20, 23, 39]
  • Stream.peek() : 중간 값을 확인하는 메소드이다. 값이 변하지 않는다.
int sum = IntStream.of(1, 3, 5, 7, 9)
  .peek(System.out::println)
  .sum();

결과 만들기

Calculation

스트림 요소들을 연산하여 결과를 도출시킨다. 기본은 long 타입이지만, 평균, 최소, 최대의 경우에는 표현할 수가 없기 때문에 Optional로 리턴이 된다.

long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();
OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
OptionalInt max = IntStream.of(1, 3, 5, 7, 9).max();
// Optional에서 ifPresent를 사용
DoubleStream.of(1.1, 2.2, 3.3, 4.4, 5.5)
  .average()
  .ifPresent(System.out::println);

Reduction

스트림 요소들을 계산할 수 있는 메소드를 활용하여 중첩 계산한다.

// 람다식을 넣어 합연산을 진행
OptionalInt reduced = 
  IntStream.range(1, 4) // [1, 2, 3]
  .reduce((a, b) -> {
    return Integer.sum(a, b);
  });
  
// 초기값부터 연산하는 첫번째 파타미터
int reducedTwoParams = IntStream.range(1, 4) // [1, 2, 3]
  .reduce(10, Integer::sum); // method reference
  
 // 병렬 스트림에서만 동작하는 마지막 파라미터 Combiner
Integer reducedParallel = Arrays.asList(1, 2, 3)
  .parallelStream()
  .reduce(10,
          Integer::sum,
          (a, b) -> {
            System.out.println("combiner was called");
            return a + b;
          });

Collection

Collector 타입의 인자를 받아서 원하는 Collection으로 반환할 수 있는 메소드이다.

  • Collectors.toList() : List 타입으로 반환한다.
  • Collectors.joining(구분자, 시작문자, 끝문자) : 요소를 합쳐 스트링으로 반환한다.
  • Collectors.averageingInt()/summingInt() : 평균 또는 합을 반환한다. 객체라면 파라미터 안에 원하는 값을 넣어 계산한다. (ex. getAmount)
  • Collectors.groupingBy() : 특정 요소를 기준으로 그룹화한 후 Map으로 리턴한다.
  • Collectors.partitioningBy(func) : 요소의 조건을 기준으로 boolean으로 나눠진 Map을 리턴한다.
List<String> collectorCollection = productList.stream()
    .map(Product::getName).collect(Collectors.toList());
// [potatoes, orange, lemon, bread, sugar]

tring listToString = productList.stream().map(Product::getName)
  .collect(Collectors.joining(", ", "<", ">"));
// <potatoes, orange, lemon, bread, sugar>

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

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

Map<Integer, List<Product>> collectorMapOfLists = productList.stream()
.collect(Collectors.groupingBy(Product::getAmount));
// {23=[Product{amount=23, name='potatoes'}, Product{amount=23, name='bread'}], 13=[Product{amount=13, name='lemon'}, Product{amount=13, name='sugar'}], 14=[Product{amount=14, name='orange'}]}

Map<Boolean, List<Product>> mapPartitioned = productList.stream()
  .collect(Collectors.partitioningBy(el -> el.getAmount() > 15));
// {false=[Product{amount=14, name='orange'}, Product{amount=13, name='lemon'}, Product{amount=13, name='sugar'}], true=[Product{amount=23, name='potatoes'}, Product{amount=23, name='bread'}]}

참고 및 읽을거리

Stream API 공식문서 (메소드)

Stream 총정리

Stream API와 For-Loop

profile
끄적끄적 코딩일기

0개의 댓글