JAVA 8 Stream

김건우·2022년 12월 11일
3

JAVA 8

목록 보기
1/3
post-thumbnail

컬렉션과 스트림의 차이

  • 데이터를 언제 계산 하느냐가 컬렉션과 스트림의 가장 큰 차이이다.

컬렉션

  1. 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조이다.
  2. 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기전에 계산되어야 한다. 컬렉션은 요소를 추가하거나 컬렉션의 요소르 삭제할 수 있다.
  3. 모든 값을 계산할 때까지 기다린다는 의미

스트림

  1. 반면 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조이다.이것을 다시말하자면 스트림에 요소를 추가하거나 요소를 제거할 수 없다.
  2. 필요할 때만 값을 계산한다는 의미

UnsupportedOpertationException

컬렉션과 스트림의 차이를 말하였다.
그 중에 하나인 컬렉션은 요소를 추가하거나 삭제할 수 있지만, 스트림은 요소를 갱신할 수 있지만 추가하거나 삭제할 수 없다.. 만약 요소를 추가하려 하면 UnsupportedOpertationException 예외가 발생한다.

스트림은 게으르게 만들어지는 컬렉션과 같다. 사용자가 데이터를 요청할 때만 값을 계산한다
-🎈재밌게 읽어본 모던 인 액션 자바라는 책의 문장🎈-

Stream

딱 한 번만 탐색할 수 있다.

  • 한 번 탐색한 요소를 다시 탐색하려면 초기 데니터 소스에서 새로운 스트림을 생성해야한다.
  • 재사용한다면 java.lang.IllegalStateException 발생
@SpringBootTest
public class StreamTest {

    public static void main(String[] args) {

        List<String> language = Arrays.asList("자바8", "자바스크립트", "C언어");
        Stream<String> stream = language.stream();

        stream.forEach(System.out::println);
        stream.forEach(System.out::println); // <- 재사용 안된다.
    }

}
  • 아래는 위의 코드를 실행 시 출력과 발생하는 예외
자바8
자바스크립트
C언어
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.base/java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
	at com.spring.jpadata.StreamTest.main(StreamTest.java:18)

스트림 적용할 Dish 클래스

package com.spring.jpadata.dto.lamda;

import lombok.Builder;
import lombok.Getter;

@Getter
public class Dish {

   private  String name;

   private boolean vegetarian;

   private int calories;

   private Type type;

   @Builder
   public Dish(String name, boolean vegetarian, int calories, Type type) {
       this.name = name;
       this.vegetarian = vegetarian;
       this.calories = calories;
       this.type = type;
   }
   
   public enum Type{ MEAT, FISH , OTHER}
}

Stream 적용

다음의 아래의 코드는 Stream을 사용하여 만든 코드이고
1. 300 칼로리 초과하는 음식
2. 음식의 이름으로 추출
3. 선착순 3개 라는 요구사항이 주어지는 코드이다.

package com.spring.jpadata.dto.lamda;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class doLamda {

   public static void main(String[] args) {
       List<Dish> menu = Arrays.asList(
               new Dish("pork", false, 800, Dish.Type.MEAT),
               new Dish("beef", false, 700, Dish.Type.MEAT),
               new Dish("chicken", false, 400, Dish.Type.MEAT),
               new Dish("french", true, 530, Dish.Type.OTHER),
               new Dish("pizza", true, 550, Dish.Type.OTHER),
               new Dish("salmon", false, 450, Dish.Type.FISH)
       );

       List<String> threeHighCalDish = menu.stream()
               .filter(dish -> dish.getCalories() > 300)
               .map(Dish::getName)
               .limit(3)
               .collect(Collectors.toList());

       System.out.println(threeHighCalDish);
   }
}
  • 위의 코드에서 collect를 제외한 모든 연산은 서로 파이프라인을 형성할 수 있도록 스트림을 반환하는 메소드이다.
  • collect : 스트림을 다른 형식으로 변환하는 메소드이다. 현재로서는 특정 결과로 변환시키는 기능을 수행하는 정도라고 하겠다. 파이프라인을 실행한 다음에 닫는다.

연산 처리 과정

  • filter -> map -> limit -> collect

중간연산과 최종연산

  • 즁간연산
연산형식반환 형식연산의 인수함수 디스크럽터
filter중간 연산StreamPredicateT -> boolean
map중간 연산StreamFunctionT -> R
limit중간 연산Stream
sorted중간 연산StreamComparator(T,T) -> int
distinct중간 연산Stream고유값 필터링
  • 최종연산
연산형식반환 형식목적
foreach최종 연산void스트림의 각 요소를 소비하면서 람다를 적용한다
count최종 연산Long스트림의 요소 개수를 반환한다
collect최종 연산void스트림을 리듀스해서 리스트,맵,정수 형식의 컬렉션을 만들어준다.

filter -> 필터링하기(boolean)

  • 다음은 채식음식을 stream으로 연산한 코드이다.
 public static void main(String[] args) {
        List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french", true, 530, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("salmon", false, 450, Dish.Type.FISH)
        );

        List<Dish> collect = menu.stream()
                .filter(Dish::isVegetarian) // <= 채식 요리인지 확인하는 메서드이다.
                .collect(toList());

        System.out.println("collect = " + collect);
    }

distinct -> 고유 요소 필터링하기

스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드를 지원한다.
고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정된다.

  • 따라서 커스텀 클래스를 제작했을 시 hashCode와 equals를 재정의 해주지 않았다면 distict는 정상적으로 작동하지 않는다.
  • 예룰 들어서 다음의 코드는 리스트의 모든 짝수를 선택하고 중복을 필터링하는 코드이다.
 public static void main(String[] args) {

        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);

        numbers.stream()
                .filter(i -> i % 2 == 0)
                .distinct() // -> 고유 요소로 필터링 해주는 메서드
                .forEach(System.out::println);
    }
  • stream 연산 흐름도

filter와 takeWhile 의 차이점

  • filter는 조건에 대해 다 검사하며 참인것만 다음으로 넘어간다
  • takeWhile은 조건에 대해 참이 아닐경우 바로 거기서 멈추게 된다

       //filter 이용시
       Stream.of(1,2,3,4,5,6,7,8,9)
                .filter(n -> n%2 == 0)
                .forEach(System.out::println);
		//takeWhile 이용시
        Stream.of(2,4,3,4,5,6,7,8,9)
                .takeWhile(n -> n%2 == 0)
                .forEach(System.out::println)
  • 위 코드의 각 출력문
filter 이용시
2
4
6
8
takeWhile 이용시
2
4

reduce 요소를 하나의 데이터로 만들기

앞서 Dish라는 객체에는 칼로리라는 필드가 존재했다. reduce(영어 번역으로는 : 줄이다 라는 뜻)를 사용하여 총 칼로리를 계산해 보갰다

  public static void main(String[] args) {
				
  			List<Dish> menu = Arrays.asList(
                    new Dish("pork", false, 800, Dish.Type.MEAT),
                    new Dish("beef", false, 700, Dish.Type.MEAT),
                    new Dish("chicken", false, 400, Dish.Type.MEAT),
                    new Dish("french", true, 530, Dish.Type.OTHER),
                    new Dish("pizza", true, 550, Dish.Type.OTHER),
                    new Dish("salmon", false, 450, Dish.Type.FISH)
            );

        Integer sumOfCalories = menu.stream()
                .map(Dish::getCalories)
                .reduce((a, b) -> a + b).get(); 
		// reduce를 사용하면 Optional로 반환됨
        System.out.println("sumOfCalories = " + sumOfCalories);
    }

reduce()는 인자로 BinaryOperator 객체를 받는데, BinaryOperator는 T 타입의 인자 두개를 받고 T 타입의 객체를 리턴하는 함수형 인터페이스이다. BinaryOperator는 (total, n) -> total + n와 같은 형식으로 인자를 받아서 처리해준다.

forEach 활용

  • 리턴타입 void

    public static void main(String[] args) {
    			//student : 이름 / 점수
          List<Student> list = Arrays.asList(
                  new Student("홍길동",82),
                  new Student("김남준",90),
                  new Student("주몽",100),
                  new Student("박혁거세",80),
                  new Student("온조",70)
          );
    
          list.stream().forEach(
                  student ->{
                      String name = student.getName();
                      int age = student.getAge();
                      System.out.println("이름 : " + name + "나이 : "+age);
                  }
          );
    
      }
  • 출력문 🔽

이름 : 홍길동 나이 : 82
이름 : 김남준 나이 : 90
이름 : 주몽 나이 : 100
이름 : 박혁거세 나이 : 80
이름 : 온조 나이 : 70
profile
Live the moment for the moment.

0개의 댓글